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