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