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.
6 REGISTER_WEAPON(MINE_LAYER, w_minelayer, IT_ROCKETS, 4, WEP_FLAG_NORMAL | WEP_TYPE_SPLASH, BOT_PICKUP_RATING_HIGH, "minelayer", "minelayer", "Mine Layer");
9 .float minelayer_detonate;
10 .float mine_number, mine_time;
12 void spawnfunc_weapon_minelayer (void)
14 weapon_defaultspawnfunc(WEP_MINE_LAYER);
17 void W_Mine_RespawnEntity ()
21 newmine.classname = self.classname;
23 newmine.bot_dodge = self.bot_dodge;
24 newmine.bot_dodgerating = self.bot_dodgerating;
26 newmine.owner = self.owner;
27 setsize(newmine, '-4 -4 -4', '4 4 4');
28 setorigin(newmine, self.origin);
29 setmodel(newmine, "models/mine.md3");
30 newmine.angles = vectoangles(-trace_plane_normal);
32 newmine.takedamage = self.takedamage;
33 newmine.damageforcescale = self.damageforcescale;
34 newmine.health = self.health;
35 newmine.event_damage = self.event_damage;
37 newmine.movetype = MOVETYPE_NONE;
38 newmine.projectiledeathtype = self.projectiledeathtype;
40 newmine.mine_number = self.mine_number;
41 newmine.mine_time = self.mine_time;
43 newmine.touch = SUB_Null;
44 newmine.think = self.think;
45 newmine.nextthink = time;
46 newmine.cnt = self.cnt;
47 newmine.flags = self.flags;
53 void W_Mine_Explode ()
55 if(other.takedamage == DAMAGE_AIM)
56 if(other.classname == "player")
57 if(IsDifferentTeam(self.owner, other))
59 AnnounceTo(self.owner, "airshot");
61 self.event_damage = SUB_Null;
62 self.takedamage = DAMAGE_NO;
64 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);
66 if (self.owner.weapon == WEP_MINE_LAYER)
68 if(self.owner.ammo_rockets < cvar("g_balance_minelayer_ammo"))
70 self.owner.cnt = WEP_MINE_LAYER;
71 ATTACK_FINISHED(self.owner) = time;
72 self.owner.switchweapon = w_getbestweapon(self.owner);
78 void W_Mine_DoRemoteExplode ()
80 self.event_damage = SUB_Null;
81 self.takedamage = DAMAGE_NO;
83 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);
85 if (self.owner.weapon == WEP_MINE_LAYER)
87 if(self.owner.ammo_rockets < cvar("g_balance_minelayer_ammo"))
89 self.owner.cnt = WEP_MINE_LAYER;
90 ATTACK_FINISHED(self.owner) = time;
91 self.owner.switchweapon = w_getbestweapon(self.owner);
97 void W_Mine_RemoteExplode()
99 if(self.owner.deadflag == DEAD_NO)
100 if((self.spawnshieldtime >= 0)
101 ? (time >= self.spawnshieldtime) // timer
102 : (vlen(NearestPointOnBox(self.owner, self.origin) - self.origin) > cvar("g_balance_minelayer_radius")) // safety device
105 W_Mine_DoRemoteExplode();
109 void W_Mine_ProximityExplode()
111 // make sure no friend is in the mine's radius. If there is any, explosion is delayed until he's at a safe distance
112 if(cvar("g_balance_minelayer_protection"))
115 head = findradius(self.origin, cvar("g_balance_minelayer_radius"));
118 if(head == self.owner || !IsDifferentTeam(head, self.owner))
128 void W_Mine_Think (void)
132 self.nextthink = time;
136 self.projectiledeathtype |= HITTYPE_BOUNCE;
141 // set the mine for detonation when a foe gets too close
142 head = findradius(self.origin, cvar("g_balance_minelayer_detectionradius"));
145 if(head.classname == "player" && head.deadflag == DEAD_NO)
146 if(head != self.owner && IsDifferentTeam(head, self.owner)) // don't trigger for team mates
149 spamsound (self, CHAN_PROJECTILE, "weapons/mine_trigger.wav", VOL_BASE, ATTN_NORM);
150 self.mine_time = time + cvar("g_balance_minelayer_time");
155 // explode if it's time to
156 if(self.mine_time && time >= self.mine_time)
157 W_Mine_ProximityExplode();
160 if (self.owner.weapon == WEP_MINE_LAYER)
161 if (self.owner.deadflag == DEAD_NO)
162 if (self.minelayer_detonate)
163 W_Mine_RemoteExplode();
165 if(self.csqcprojectile_clientanimate == 0)
166 UpdateCSQCProjectile(self);
169 void W_Mine_Touch (void)
172 if(!other || (other.takedamage != DAMAGE_AIM && other.movetype == MOVETYPE_NONE))
174 spamsound (self, CHAN_PROJECTILE, "weapons/mine_stick.wav", VOL_BASE, ATTN_NORM);
175 self.movetype = MOVETYPE_NONE; // lock the mine in place
177 W_Mine_RespawnEntity();
179 else if(self.movetype != MOVETYPE_NONE) // don't unstick a locked mine when someone touches it
180 self.velocity = '0 0 0';
183 void W_Mine_Damage (entity inflictor, entity attacker, float damage, float deathtype, vector hitloc, vector force)
185 if (self.health <= 0)
187 self.health = self.health - damage;
188 self.angles = vectoangles(self.velocity);
189 if (self.health <= 0)
190 W_PrepareExplosionByDamage(attacker, W_Mine_Explode);
193 void W_Mine_Attack (void)
198 // scan how many mines we placed, and return if we reached our limit
199 if(cvar("g_balance_minelayer_limit"))
201 self.mine_number = 0;
202 for(mine = world; (mine = find(mine, classname, "mine")); ) if(mine.owner == self)
203 self.mine_number += 1;
205 if(self.mine_number >= cvar("g_balance_minelayer_limit"))
207 // the refire delay keeps this message from being spammed
208 sprint(self, strcat("You cannot place more than ^2", cvar_string("g_balance_minelayer_limit"), " ^7mines at a time\n") );
209 play2(self, "weapons/unavailable.wav");
214 if not(self.items & IT_UNLIMITED_WEAPON_AMMO)
215 self.ammo_rockets = self.ammo_rockets - cvar("g_balance_minelayer_ammo");
217 W_SetupShot_ProjectileSize (self, '-4 -4 -4', '4 4 4', FALSE, 5, "weapons/mine_fire.wav", cvar("g_balance_minelayer_damage"));
218 pointparticles(particleeffectnum("rocketlauncher_muzzleflash"), w_shotorg, w_shotdir * 1000, 1);
220 mine = WarpZone_RefSys_SpawnSameRefSys(self);
222 if(cvar("g_balance_minelayer_detonatedelay") >= 0)
223 mine.spawnshieldtime = time + cvar("g_balance_minelayer_detonatedelay");
225 mine.spawnshieldtime = -1;
226 mine.classname = "mine";
227 mine.bot_dodge = TRUE;
228 mine.bot_dodgerating = cvar("g_balance_minelayer_damage") * 2; // * 2 because it can detonate inflight which makes it even more dangerous
230 mine.takedamage = DAMAGE_YES;
231 mine.damageforcescale = cvar("g_balance_minelayer_damageforcescale");
232 mine.health = cvar("g_balance_minelayer_health");
233 mine.event_damage = W_Mine_Damage;
235 mine.movetype = MOVETYPE_TOSS;
236 PROJECTILE_MAKETRIGGER(mine);
237 mine.projectiledeathtype = WEP_MINE_LAYER;
238 setsize (mine, '-4 -4 -4', '4 4 4'); // give it some size so it can be shot
240 setorigin (mine, w_shotorg - v_forward * 4); // move it back so it hits the wall at the right point
241 W_SetupProjectileVelocity(mine, cvar("g_balance_minelayer_speed"), 0);
242 mine.angles = vectoangles (mine.velocity);
244 mine.touch = W_Mine_Touch;
245 mine.think = W_Mine_Think;
246 mine.nextthink = time;
247 mine.cnt = time + cvar("g_balance_minelayer_lifetime");
248 mine.flags = FL_PROJECTILE;
250 CSQCProjectile(mine, FALSE, PROJECTILE_MINE, TRUE);
252 // muzzle flash for 1st person view
254 setmodel (flash, "models/flash.md3"); // precision set below
255 SUB_SetFade (flash, time, 0.1);
256 flash.effects = EF_ADDITIVE | EF_FULLBRIGHT | EF_LOWPRECISION;
257 W_AttachToShotorg(flash, '5 0 0');
262 void spawnfunc_weapon_minelayer (void); // defined in t_items.qc
264 float w_minelayer(float req)
270 // aim and decide to fire if appropriate
271 self.BUTTON_ATCK = bot_aim(cvar("g_balance_minelayer_speed"), 0, cvar("g_balance_minelayer_lifetime"), FALSE);
272 if(skill >= 2) // skill 0 and 1 bots won't detonate mines!
274 // decide whether to detonate mines
275 local entity mine, targetlist, targ;
276 local float edgedamage, coredamage, edgeradius, recipricoledgeradius, d;
277 local float selfdamage, teamdamage, enemydamage;
278 edgedamage = cvar("g_balance_minelayer_edgedamage");
279 coredamage = cvar("g_balance_minelayer_damage");
280 edgeradius = cvar("g_balance_minelayer_radius");
281 recipricoledgeradius = 1 / edgeradius;
285 targetlist = findchainfloat(bot_attack, TRUE);
286 mine = find(world, classname, "mine");
289 if (mine.owner != self)
291 mine = find(mine, classname, "mine");
297 d = vlen(targ.origin + (targ.mins + targ.maxs) * 0.5 - mine.origin);
298 d = bound(0, edgedamage + (coredamage - edgedamage) * sqrt(1 - d * recipricoledgeradius), 10000);
299 // count potential damage according to type of target
301 selfdamage = selfdamage + d;
302 else if (targ.team == self.team && teams_matter)
303 teamdamage = teamdamage + d;
304 else if (bot_shouldattack(targ))
305 enemydamage = enemydamage + d;
308 mine = find(mine, classname, "mine");
310 local float desirabledamage;
311 desirabledamage = enemydamage;
312 if (teamplay != 1 && time > self.invincible_finished && time > self.spawnshieldtime)
313 desirabledamage = desirabledamage - selfdamage * cvar("g_balance_selfdamagepercent");
314 if (self.team && teamplay != 1)
315 desirabledamage = desirabledamage - teamdamage;
317 mine = find(world, classname, "mine");
320 if (mine.owner != self)
322 mine = find(mine, classname, "mine");
325 makevectors(mine.v_angle);
327 if (skill > 9) // normal players only do this for the target they are tracking
333 (v_forward * normalize(mine.origin - targ.origin)< 0.1)
334 && desirabledamage > 0.1*coredamage
335 )self.BUTTON_ATCK2 = TRUE;
339 local float distance; distance= bound(300,vlen(self.origin-self.enemy.origin),30000);
340 //As the distance gets larger, a correct detonation gets near imposible
341 //Bots are assumed to use the mine spawnfunc_light to see if the mine gets near a player
342 if(v_forward * normalize(mine.origin - self.enemy.origin)< 0.1)
343 if(self.enemy.classname == "player")
344 if(desirabledamage >= 0.1*coredamage)
345 if(random()/distance*300 > frametime*bound(0,(10-skill)*0.2,1))
346 self.BUTTON_ATCK2 = TRUE;
347 // dprint(ftos(random()/distance*300),">");dprint(ftos(frametime*bound(0,(10-skill)*0.2,1)),"\n");
350 mine = find(mine, classname, "mine");
352 // if we would be doing at X percent of the core damage, detonate it
353 // but don't fire a new shot at the same time!
354 if (desirabledamage >= 0.75 * coredamage) //this should do group damage in rare fortunate events
355 self.BUTTON_ATCK2 = TRUE;
356 if ((skill > 6.5) && (selfdamage > self.health))
357 self.BUTTON_ATCK2 = FALSE;
358 //if(self.BUTTON_ATCK2 == TRUE)
359 // dprint(ftos(desirabledamage),"\n");
360 if (self.BUTTON_ATCK2 == TRUE) self.BUTTON_ATCK = FALSE;
363 else if (req == WR_THINK)
365 if (self.BUTTON_ATCK)
367 if(weapon_prepareattack(0, cvar("g_balance_minelayer_refire")))
370 weapon_thinkf(WFRAME_FIRE1, cvar("g_balance_minelayer_animtime"), w_ready);
374 if (self.BUTTON_ATCK2)
377 for(mine = world; (mine = find(mine, classname, "mine")); ) if(mine.owner == self)
379 if(!mine.minelayer_detonate)
381 mine.minelayer_detonate = TRUE;
386 sound (self, CHAN_WEAPON2, "weapons/mine_det.wav", VOL_BASE, ATTN_NORM);
389 else if (req == WR_PRECACHE)
391 precache_model ("models/flash.md3");
392 precache_model ("models/weapons/g_minelayer.md3");
393 precache_model ("models/weapons/v_minelayer.md3");
394 precache_model ("models/weapons/h_minelayer.iqm");
395 precache_sound ("weapons/mine_det.wav");
396 precache_sound ("weapons/mine_fire.wav");
397 precache_sound ("weapons/mine_stick.wav");
398 precache_sound ("weapons/mine_trigger.wav");
400 else if (req == WR_SETUP)
402 weapon_setup(WEP_MINE_LAYER);
404 else if (req == WR_CHECKAMMO1)
406 // don't switch while placing a mine
407 if ((ATTACK_FINISHED(self) <= time || self.weapon != WEP_MINE_LAYER)
408 && self.ammo_rockets < cvar("g_balance_minelayer_ammo"))
411 else if (req == WR_CHECKAMMO2)
417 float w_minelayer(float req)
419 if(req == WR_IMPACTEFFECT)
422 org2 = w_org + w_backoff * 12;
423 pointparticles(particleeffectnum("rocket_explode"), org2, '0 0 0', 1);
425 sound(self, CHAN_PROJECTILE, "weapons/mine_exp.wav", VOL_BASE, ATTN_NORM);
427 else if(req == WR_PRECACHE)
429 precache_sound("weapons/mine_exp.wav");
431 else if (req == WR_SUICIDEMESSAGE)
432 w_deathtypestring = "%s exploded";
433 else if (req == WR_KILLMESSAGE)
435 if(w_deathtype & HITTYPE_BOUNCE) // (remote detonation)
436 w_deathtypestring = "%s got too close to %s's mine";
437 else if(w_deathtype & HITTYPE_SPLASH)
438 w_deathtypestring = "%s almost dodged %s's mine";
440 w_deathtypestring = "%s stepped on %s's mine";