]> de.git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/blob - qcsrc/server/w_minelayer.qc
fix W_Mine_RespawnEntity
[xonotic/xonotic-data.pk3dir.git] / qcsrc / server / w_minelayer.qc
1 /*TODO list (things left to do before this weapon should be ready, delete once it's all done):
2 - The mine model needs to face properly when it sticks to a surface. Once we'll have a correct mine model, we can't afford the model facing any way it falls to the ground. Should probably look at the porto code to see how portals face in the right direction when sticking to walls. 
3 */
4
5 #ifdef REGISTER_WEAPON
6 REGISTER_WEAPON(MINE_LAYER, w_minelayer, IT_ROCKETS, 4, WEP_FLAG_NORMAL | WEP_TYPE_SPLASH, BOT_PICKUP_RATING_HIGH, "minelayer", "minelayer", "Mine Layer");
7 #else
8 #ifdef SVQC
9 .float minelayer_detonate;
10 .float mine_number, mine_time;
11
12 void spawnfunc_weapon_minelayer (void)
13 {
14         weapon_defaultspawnfunc(WEP_MINE_LAYER);
15 }
16
17 void W_Mine_RespawnEntity ()
18 {
19         entity newmine;
20         newmine = spawn();
21         newmine.classname = self.classname;
22
23         newmine.bot_dodge = self.bot_dodge;
24         newmine.bot_dodgerating = self.bot_dodgerating;
25
26         newmine.owner = self.owner;
27         setsize(newmine, '-4 -4 -4', '4 4 4');
28         setorigin(newmine, self.origin);
29         setmodel(newmine, "models/mine.md3");
30         newmine.angles = vectoangles(-trace_plane_normal);
31
32         newmine.takedamage = self.takedamage;
33         newmine.damageforcescale = self.damageforcescale;
34         newmine.health = self.health;
35         newmine.event_damage = self.event_damage;
36
37         newmine.movetype = MOVETYPE_NONE;
38         newmine.projectiledeathtype = self.projectiledeathtype;
39
40         newmine.mine_number = self.mine_number;
41         newmine.mine_time = self.mine_time;
42
43         newmine.touch = SUB_Null;
44         newmine.think = self.think;
45         newmine.nextthink = time;
46         newmine.cnt = self.cnt;
47         newmine.flags = self.flags;
48
49         remove(self);
50         self = newmine;
51 }
52
53 void W_Mine_Explode ()
54 {
55         if(other.takedamage == DAMAGE_AIM)
56                 if(other.classname == "player")
57                         if(IsDifferentTeam(self.owner, other))
58                                 if(IsFlying(other))
59                                         AnnounceTo(self.owner, "airshot");
60
61         self.event_damage = SUB_Null;
62         self.takedamage = DAMAGE_NO;
63
64         RadiusDamage (self, self.owner, cvar("g_balance_minelayer_damage"), cvar("g_balance_minelayer_edgedamage"), cvar("g_balance_minelayer_radius"), world, cvar("g_balance_minelayer_force"), self.projectiledeathtype, other);
65
66         if (self.owner.weapon == WEP_MINE_LAYER)
67         {
68                 if(self.owner.ammo_rockets < cvar("g_balance_minelayer_ammo"))
69                 {
70                         self.owner.cnt = WEP_MINE_LAYER;
71                         ATTACK_FINISHED(self.owner) = time;
72                         self.owner.switchweapon = w_getbestweapon(self.owner);
73                 }
74         }
75         remove (self);
76 }
77
78 void W_Mine_DoRemoteExplode ()
79 {
80         self.event_damage = SUB_Null;
81         self.takedamage = DAMAGE_NO;
82
83         RadiusDamage (self, self.owner, cvar("g_balance_minelayer_remote_damage"), cvar("g_balance_minelayer_remote_edgedamage"), cvar("g_balance_minelayer_remote_radius"), world, cvar("g_balance_minelayer_remote_force"), self.projectiledeathtype | HITTYPE_BOUNCE, world);
84
85         if (self.owner.weapon == WEP_MINE_LAYER)
86         {
87                 if(self.owner.ammo_rockets < cvar("g_balance_minelayer_ammo"))
88                 {
89                         self.owner.cnt = WEP_MINE_LAYER;
90                         ATTACK_FINISHED(self.owner) = time;
91                         self.owner.switchweapon = w_getbestweapon(self.owner);
92                 }
93         }
94         remove (self);
95 }
96
97 void W_Mine_RemoteExplode()
98 {
99         if(self.owner.deadflag == DEAD_NO)
100                 if((self.spawnshieldtime >= 0)
101                         ? (time >= self.spawnshieldtime) // timer
102                         : (vlen(NearestPointOnBox(self.owner, self.origin) - self.origin) > cvar("g_balance_minelayer_radius")) // safety device
103                 )
104                 {
105                         W_Mine_DoRemoteExplode();
106                 }
107 }
108
109 void W_Mine_ProximityExplode()
110 {
111         // make sure no friend is in the mine's radius. If there is any, explosion is delayed until he's at a safe distance
112         if(cvar("g_balance_minelayer_protection"))
113         {
114                 entity head;
115                 head = findradius(self.origin, cvar("g_balance_minelayer_radius"));
116                 while(head)
117                 {
118                         if(head == self.owner || !IsDifferentTeam(head, self.owner))
119                                 return;
120                         head = head.chain;
121                 }
122         }
123
124         self.mine_time = 0;
125         W_Mine_Explode();
126 }
127
128 void W_Mine_Think (void)
129 {
130         entity head;
131
132         self.nextthink = time;
133         if (time > self.cnt)
134         {
135                 other = world;
136                 self.projectiledeathtype |= HITTYPE_BOUNCE;
137                 W_Mine_Explode ();
138                 return;
139         }
140
141         // set the mine for detonation when a foe gets too close
142         head = findradius(self.origin, cvar("g_balance_minelayer_detectionradius"));
143         while(head)
144         {
145                 if(head.classname == "player" && head.deadflag == DEAD_NO)
146                 if(head != self.owner && IsDifferentTeam(head, self.owner)) // don't trigger for team mates
147                 if(!self.mine_time)
148                 {
149                         spamsound (self, CHAN_PROJECTILE, "weapons/mine_trigger.wav", VOL_BASE, ATTN_NORM);
150                         self.mine_time = time + cvar("g_balance_minelayer_time");
151                 }
152                 head = head.chain;
153         }
154
155         // explode if it's time to
156         if(self.mine_time && time >= self.mine_time)
157                 W_Mine_ProximityExplode();
158
159         // remote detonation
160         if (self.owner.weapon == WEP_MINE_LAYER)
161         if (self.owner.deadflag == DEAD_NO)
162         if (self.minelayer_detonate)
163                 W_Mine_RemoteExplode();
164
165         if(self.csqcprojectile_clientanimate == 0)
166                 UpdateCSQCProjectile(self);
167 }
168
169 void W_Mine_Touch (void)
170 {
171         PROJECTILE_TOUCH;
172         if(!other || (other.takedamage != DAMAGE_AIM && other.movetype == MOVETYPE_NONE))
173         {
174                 spamsound (self, CHAN_PROJECTILE, "weapons/mine_stick.wav", VOL_BASE, ATTN_NORM);
175                 self.movetype = MOVETYPE_NONE; // lock the mine in place
176
177                 W_Mine_RespawnEntity();
178         }
179         else if(self.movetype != MOVETYPE_NONE) // don't unstick a locked mine when someone touches it
180                 self.velocity = '0 0 0';
181 }
182
183 void W_Mine_Damage (entity inflictor, entity attacker, float damage, float deathtype, vector hitloc, vector force)
184 {
185         if (self.health <= 0)
186                 return;
187         self.health = self.health - damage;
188         self.angles = vectoangles(self.velocity);
189         if (self.health <= 0)
190                 W_PrepareExplosionByDamage(attacker, W_Mine_Explode);
191 }
192
193 void W_Mine_Attack (void)
194 {
195         local entity mine;
196         local entity flash;
197
198         // scan how many mines we placed, and return if we reached our limit
199         if(cvar("g_balance_minelayer_limit"))
200         {
201                 self.mine_number = 0;
202                 for(mine = world; (mine = find(mine, classname, "mine")); ) if(mine.owner == self)
203                         self.mine_number += 1;
204
205                 if(self.mine_number >= cvar("g_balance_minelayer_limit"))
206                 {
207                         // the refire delay keeps this message from being spammed
208                         sprint(self, strcat("You cannot place more than ^2", cvar_string("g_balance_minelayer_limit"), " ^7mines at a time\n") );
209                         play2(self, "weapons/unavailable.wav");
210                         return;
211                 }
212         }
213
214         if not(self.items & IT_UNLIMITED_WEAPON_AMMO)
215                 self.ammo_rockets = self.ammo_rockets - cvar("g_balance_minelayer_ammo");
216
217         W_SetupShot_ProjectileSize (self, '-4 -4 -4', '4 4 4', FALSE, 5, "weapons/mine_fire.wav", cvar("g_balance_minelayer_damage"));
218         pointparticles(particleeffectnum("rocketlauncher_muzzleflash"), w_shotorg, w_shotdir * 1000, 1);
219
220         mine = WarpZone_RefSys_SpawnSameRefSys(self);
221         mine.owner = self;
222         if(cvar("g_balance_minelayer_detonatedelay") >= 0)
223                 mine.spawnshieldtime = time + cvar("g_balance_minelayer_detonatedelay");
224         else
225                 mine.spawnshieldtime = -1;
226         mine.classname = "mine";
227         mine.bot_dodge = TRUE;
228         mine.bot_dodgerating = cvar("g_balance_minelayer_damage") * 2; // * 2 because it can detonate inflight which makes it even more dangerous
229
230         mine.takedamage = DAMAGE_YES;
231         mine.damageforcescale = cvar("g_balance_minelayer_damageforcescale");
232         mine.health = cvar("g_balance_minelayer_health");
233         mine.event_damage = W_Mine_Damage;
234
235         mine.movetype = MOVETYPE_TOSS;
236         PROJECTILE_MAKETRIGGER(mine);
237         mine.projectiledeathtype = WEP_MINE_LAYER;
238         setsize (mine, '-4 -4 -4', '4 4 4'); // give it some size so it can be shot
239
240         setorigin (mine, w_shotorg - v_forward * 4); // move it back so it hits the wall at the right point
241         W_SetupProjectileVelocity(mine, cvar("g_balance_minelayer_speed"), 0);
242         mine.angles = vectoangles (mine.velocity);
243
244         mine.touch = W_Mine_Touch;
245         mine.think = W_Mine_Think;
246         mine.nextthink = time;
247         mine.cnt = time + cvar("g_balance_minelayer_lifetime");
248         mine.flags = FL_PROJECTILE;
249
250         CSQCProjectile(mine, FALSE, PROJECTILE_MINE, TRUE);
251
252         // muzzle flash for 1st person view
253         flash = spawn ();
254         setmodel (flash, "models/flash.md3"); // precision set below
255         SUB_SetFade (flash, time, 0.1);
256         flash.effects = EF_ADDITIVE | EF_FULLBRIGHT | EF_LOWPRECISION;
257         W_AttachToShotorg(flash, '5 0 0');
258
259         // common properties
260 }
261
262 void spawnfunc_weapon_minelayer (void); // defined in t_items.qc
263
264 float w_minelayer(float req)
265 {
266         entity mine;
267         float minfound;
268         if (req == WR_AIM)
269         {
270                 // aim and decide to fire if appropriate
271                 self.BUTTON_ATCK = bot_aim(cvar("g_balance_minelayer_speed"), 0, cvar("g_balance_minelayer_lifetime"), FALSE);
272                 if(skill >= 2) // skill 0 and 1 bots won't detonate mines!
273                 {
274                         // decide whether to detonate mines
275                         local entity mine, targetlist, targ;
276                         local float edgedamage, coredamage, edgeradius, recipricoledgeradius, d;
277                         local float selfdamage, teamdamage, enemydamage;
278                         edgedamage = cvar("g_balance_minelayer_edgedamage");
279                         coredamage = cvar("g_balance_minelayer_damage");
280                         edgeradius = cvar("g_balance_minelayer_radius");
281                         recipricoledgeradius = 1 / edgeradius;
282                         selfdamage = 0;
283                         teamdamage = 0;
284                         enemydamage = 0;
285                         targetlist = findchainfloat(bot_attack, TRUE);
286                         mine = find(world, classname, "mine");
287                         while (mine)
288                         {
289                                 if (mine.owner != self)
290                                 {
291                                         mine = find(mine, classname, "mine");
292                                         continue;
293                                 }
294                                 targ = targetlist;
295                                 while (targ)
296                                 {
297                                         d = vlen(targ.origin + (targ.mins + targ.maxs) * 0.5 - mine.origin);
298                                         d = bound(0, edgedamage + (coredamage - edgedamage) * sqrt(1 - d * recipricoledgeradius), 10000);
299                                         // count potential damage according to type of target
300                                         if (targ == self)
301                                                 selfdamage = selfdamage + d;
302                                         else if (targ.team == self.team && teams_matter)
303                                                 teamdamage = teamdamage + d;
304                                         else if (bot_shouldattack(targ))
305                                                 enemydamage = enemydamage + d;
306                                         targ = targ.chain;
307                                 }
308                                 mine = find(mine, classname, "mine");
309                         }
310                         local float desirabledamage;
311                         desirabledamage = enemydamage;
312                         if (teamplay != 1 && time > self.invincible_finished && time > self.spawnshieldtime)
313                                 desirabledamage = desirabledamage - selfdamage * cvar("g_balance_selfdamagepercent");
314                         if (self.team && teamplay != 1)
315                                 desirabledamage = desirabledamage - teamdamage;
316
317                         mine = find(world, classname, "mine");
318                         while (mine)
319                         {
320                                 if (mine.owner != self)
321                                 {
322                                         mine = find(mine, classname, "mine");
323                                         continue;
324                                 }
325                                 makevectors(mine.v_angle);
326                                 targ = targetlist;
327                                 if (skill > 9) // normal players only do this for the target they are tracking
328                                 {
329                                         targ = targetlist;
330                                         while (targ)
331                                         {
332                                                 if (
333                                                         (v_forward * normalize(mine.origin - targ.origin)< 0.1)
334                                                         && desirabledamage > 0.1*coredamage
335                                                 )self.BUTTON_ATCK2 = TRUE;
336                                                 targ = targ.chain;
337                                         }
338                                 }else{
339                                         local float distance; distance= bound(300,vlen(self.origin-self.enemy.origin),30000);
340                                         //As the distance gets larger, a correct detonation gets near imposible
341                                         //Bots are assumed to use the mine spawnfunc_light to see if the mine gets near a player
342                                         if(v_forward * normalize(mine.origin - self.enemy.origin)< 0.1)
343                                                 if(self.enemy.classname == "player")
344                                                         if(desirabledamage >= 0.1*coredamage)
345                                                                 if(random()/distance*300 > frametime*bound(0,(10-skill)*0.2,1))
346                                                                         self.BUTTON_ATCK2 = TRUE;
347                                 //      dprint(ftos(random()/distance*300),">");dprint(ftos(frametime*bound(0,(10-skill)*0.2,1)),"\n");
348                                 }
349
350                                 mine = find(mine, classname, "mine");
351                         }
352                         // if we would be doing at X percent of the core damage, detonate it
353                         // but don't fire a new shot at the same time!
354                         if (desirabledamage >= 0.75 * coredamage) //this should do group damage in rare fortunate events
355                                 self.BUTTON_ATCK2 = TRUE;
356                         if ((skill > 6.5) && (selfdamage > self.health))
357                                 self.BUTTON_ATCK2 = FALSE;
358                         //if(self.BUTTON_ATCK2 == TRUE)
359                         //      dprint(ftos(desirabledamage),"\n");
360                         if (self.BUTTON_ATCK2 == TRUE) self.BUTTON_ATCK = FALSE;
361                 }
362         }
363         else if (req == WR_THINK)
364         {
365                 if (self.BUTTON_ATCK)
366                 {
367                         if(weapon_prepareattack(0, cvar("g_balance_minelayer_refire")))
368                         {
369                                 W_Mine_Attack();
370                                 weapon_thinkf(WFRAME_FIRE1, cvar("g_balance_minelayer_animtime"), w_ready);
371                         }
372                 }
373
374                 if (self.BUTTON_ATCK2)
375                 {
376                         minfound = 0;
377                         for(mine = world; (mine = find(mine, classname, "mine")); ) if(mine.owner == self)
378                         {
379                                 if(!mine.minelayer_detonate)
380                                 {
381                                         mine.minelayer_detonate = TRUE;
382                                         minfound = 1;
383                                 }
384                         }
385                         if(minfound)
386                                 sound (self, CHAN_WEAPON2, "weapons/mine_det.wav", VOL_BASE, ATTN_NORM);
387                 }
388         }
389         else if (req == WR_PRECACHE)
390         {
391                 precache_model ("models/flash.md3");
392                 precache_model ("models/weapons/g_minelayer.md3");
393                 precache_model ("models/weapons/v_minelayer.md3");
394                 precache_model ("models/weapons/h_minelayer.iqm");
395                 precache_sound ("weapons/mine_det.wav");
396                 precache_sound ("weapons/mine_fire.wav");
397                 precache_sound ("weapons/mine_stick.wav");
398                 precache_sound ("weapons/mine_trigger.wav");
399         }
400         else if (req == WR_SETUP)
401         {
402                 weapon_setup(WEP_MINE_LAYER);
403         }
404         else if (req == WR_CHECKAMMO1)
405         {
406                 // don't switch while placing a mine
407                 if ((ATTACK_FINISHED(self) <= time || self.weapon != WEP_MINE_LAYER)
408                         && self.ammo_rockets < cvar("g_balance_minelayer_ammo"))
409                         return FALSE;
410         }
411         else if (req == WR_CHECKAMMO2)
412                 return FALSE;
413         return TRUE;
414 };
415 #endif
416 #ifdef CSQC
417 float w_minelayer(float req)
418 {
419         if(req == WR_IMPACTEFFECT)
420         {
421                 vector org2;
422                 org2 = w_org + w_backoff * 12;
423                 pointparticles(particleeffectnum("rocket_explode"), org2, '0 0 0', 1);
424                 if(!w_issilent)
425                         sound(self, CHAN_PROJECTILE, "weapons/mine_exp.wav", VOL_BASE, ATTN_NORM);
426         }
427         else if(req == WR_PRECACHE)
428         {
429                 precache_sound("weapons/mine_exp.wav");
430         }
431         else if (req == WR_SUICIDEMESSAGE)
432                 w_deathtypestring = "%s exploded";
433         else if (req == WR_KILLMESSAGE)
434         {
435                 if(w_deathtype & HITTYPE_BOUNCE) // (remote detonation)
436                         w_deathtypestring = "%s got too close to %s's mine";
437                 else if(w_deathtype & HITTYPE_SPLASH)
438                         w_deathtypestring = "%s almost dodged %s's mine";
439                 else
440                         w_deathtypestring = "%s stepped on %s's mine";
441         }
442         return TRUE;
443 }
444 #endif
445 #endif