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