]> de.git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/blob - qcsrc/server/w_minelayer.qc
TODO list in the weapon file, for the important things that must be done before this...
[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 - Add protection so a mine doesn't explode if it would harm the player or a team mate, in case both an enemy and a friend are its range. This already exists for alt-fire detonation, but not for proximity detonation. Should probably be a cvared but default option.
4 - 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?
5 - Bot code for the weapon may be needed. The bot AI may not have any info about this gun yet.
6 - 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. 
7 */
8
9 #ifdef REGISTER_WEAPON
10 REGISTER_WEAPON(MINE_LAYER, w_minelayer, IT_ROCKETS, 4, WEP_FLAG_NORMAL | WEP_TYPE_SPLASH, BOT_PICKUP_RATING_HIGH, "minelayer", "minelayer", "Mine Layer");
11 #else
12 #ifdef SVQC
13 .float minelayer_detonate;
14 .float mine_number, mine_time;
15
16 void spawnfunc_weapon_minelayer (void)
17 {
18         weapon_defaultspawnfunc(WEP_MINE_LAYER);
19 }
20
21 void W_Mine_Unregister()
22 {
23         if(self.owner && self.owner.lastmine == self)
24                 self.owner.lastmine = world;
25 }
26
27 void W_Mine_Explode ()
28 {
29         W_Mine_Unregister();
30
31         if(other.takedamage == DAMAGE_AIM)
32                 if(other.classname == "player")
33                         if(IsDifferentTeam(self.owner, other))
34                                 if(IsFlying(other))
35                                         AnnounceTo(self.owner, "airshot");
36
37         self.event_damage = SUB_Null;
38         self.takedamage = DAMAGE_NO;
39
40         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);
41
42         if (self.owner.weapon == WEP_MINE_LAYER)
43         {
44                 if(self.owner.ammo_rockets < cvar("g_balance_minelayer_ammo"))
45                 {
46                         self.owner.cnt = WEP_MINE_LAYER;
47                         ATTACK_FINISHED(self.owner) = time;
48                         self.owner.switchweapon = w_getbestweapon(self.owner);
49                 }
50         }
51         remove (self);
52 }
53
54 void W_Mine_DoRemoteExplode ()
55 {
56         W_Mine_Unregister();
57
58         self.event_damage = SUB_Null;
59         self.takedamage = DAMAGE_NO;
60
61         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);
62
63         if (self.owner.weapon == WEP_MINE_LAYER)
64         {
65                 if(self.owner.ammo_rockets < cvar("g_balance_minelayer_ammo"))
66                 {
67                         self.owner.cnt = WEP_MINE_LAYER;
68                         ATTACK_FINISHED(self.owner) = time;
69                         self.owner.switchweapon = w_getbestweapon(self.owner);
70                 }
71         }
72         remove (self);
73 }
74
75 void W_Mine_RemoteExplode()
76 {
77         if(self.owner.deadflag == DEAD_NO)
78         if(self.owner.lastmine)
79         {
80                 if((self.spawnshieldtime >= 0)
81                         ? (time >= self.spawnshieldtime) // timer
82                         : (vlen(NearestPointOnBox(self.owner, self.origin) - self.origin) > cvar("g_balance_minelayer_radius")) // safety device
83                 )
84                 {
85                         W_Mine_DoRemoteExplode();
86                 }
87         }
88 }
89
90 void W_Mine_Think (void)
91 {
92         self.nextthink = time;
93         if (time > self.cnt)
94         {
95                 other = world;
96                 self.projectiledeathtype |= HITTYPE_BOUNCE;
97                 W_Mine_Explode ();
98                 return;
99         }
100
101         // detect players who are close the mine and explode if the player should detonate it
102         entity head;
103         head = findradius(self.origin, cvar("g_balance_minelayer_detectionradius"));
104
105         while(head)
106         {
107                 if(head.classname == "player" && head.deadflag == DEAD_NO)
108                 if(head != self.owner)
109                 if(IsDifferentTeam(head, self.owner)) // don't detonate for team mates
110                 if(!self.mine_time)
111                 {
112                         spamsound (self, CHAN_PROJECTILE, "weapons/mine_trigger.wav", VOL_BASE, ATTN_NORM);
113                         self.mine_time = time + cvar("g_balance_minelayer_time");
114                 }
115                 head = head.chain;
116         }
117
118         // explode if it's time
119         if(self.mine_time && time >= self.mine_time)
120         {
121                 self.mine_time = 0;
122                 W_Mine_Explode();
123         }
124
125         // remote detonation
126         if (self.owner.weapon == WEP_MINE_LAYER)
127         if (self.owner.deadflag == DEAD_NO)
128         if (self.minelayer_detonate)
129                 W_Mine_RemoteExplode();
130
131         if(self.csqcprojectile_clientanimate == 0)
132                 UpdateCSQCProjectile(self);
133 }
134
135 void W_Mine_Touch (void)
136 {
137         PROJECTILE_TOUCH;
138         spamsound (self, CHAN_PROJECTILE, "weapons/mine_stick.wav", VOL_BASE, ATTN_NORM);
139         self.movetype = MOVETYPE_NONE; // lock the mine in place
140         // TODO: make sure this doesn't cause the mine to get stuck in the air if it falls over a moving entity
141 }
142
143 void W_Mine_Damage (entity inflictor, entity attacker, float damage, float deathtype, vector hitloc, vector force)
144 {
145         if (self.health <= 0)
146                 return;
147         self.health = self.health - damage;
148         self.angles = vectoangles(self.velocity);
149         if (self.health <= 0)
150                 W_PrepareExplosionByDamage(attacker, W_Mine_Explode);
151 }
152
153 void W_Mine_Attack (void)
154 {
155         local entity mine;
156         local entity flash;
157
158         // scan how many mines we placed, and return if we reached our limit
159         if(cvar("g_balance_minelayer_limit"))
160         {
161                 entity mine;
162                 self.mine_number = 0;
163                 for(mine = world; (mine = find(mine, classname, "mine")); ) if(mine.owner == self)
164                         self.mine_number += 1;
165
166                 if(self.mine_number >= cvar("g_balance_minelayer_limit"))
167                 {
168                         // the refire delay keeps this message from being spammed
169                         sprint(self, strcat("You cannot place more than ^2", cvar_string("g_balance_minelayer_limit"), " ^7mines at a time\n") );
170                         play2(self, "weapons/unavailable.wav");
171                         return;
172                 }
173         }
174
175         if not(self.items & IT_UNLIMITED_WEAPON_AMMO)
176                 self.ammo_rockets = self.ammo_rockets - cvar("g_balance_minelayer_ammo");
177
178         W_SetupShot_ProjectileSize (self, '-4 -4 -4', '4 4 4', FALSE, 5, "weapons/mine_fire.wav", cvar("g_balance_minelayer_damage"));
179         pointparticles(particleeffectnum("rocketlauncher_muzzleflash"), w_shotorg, w_shotdir * 1000, 1);
180
181         mine = WarpZone_RefSys_SpawnSameRefSys(self);
182         mine.owner = self;
183         self.lastmine = mine;
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_speedstart"), 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