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