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.
9 REGISTER_WEAPON(MINE_LAYER, w_minelayer, IT_ROCKETS, 4, WEP_FLAG_NORMAL | WEP_TYPE_SPLASH, BOT_PICKUP_RATING_HIGH, "minelayer", "minelayer", "Mine Layer");
12 .float minelayer_detonate;
13 .float mine_number, mine_time;
15 void spawnfunc_weapon_minelayer (void)
17 weapon_defaultspawnfunc(WEP_MINE_LAYER);
20 void W_Mine_Unregister()
22 if(self.owner && self.owner.lastmine == self)
23 self.owner.lastmine = world;
26 void W_Mine_Explode ()
30 if(other.takedamage == DAMAGE_AIM)
31 if(other.classname == "player")
32 if(IsDifferentTeam(self.owner, other))
34 AnnounceTo(self.owner, "airshot");
36 self.event_damage = SUB_Null;
37 self.takedamage = DAMAGE_NO;
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);
41 if (self.owner.weapon == WEP_MINE_LAYER)
43 if(self.owner.ammo_rockets < cvar("g_balance_minelayer_ammo"))
45 self.owner.cnt = WEP_MINE_LAYER;
46 ATTACK_FINISHED(self.owner) = time;
47 self.owner.switchweapon = w_getbestweapon(self.owner);
53 void W_Mine_DoRemoteExplode ()
57 self.event_damage = SUB_Null;
58 self.takedamage = DAMAGE_NO;
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);
62 if (self.owner.weapon == WEP_MINE_LAYER)
64 if(self.owner.ammo_rockets < cvar("g_balance_minelayer_ammo"))
66 self.owner.cnt = WEP_MINE_LAYER;
67 ATTACK_FINISHED(self.owner) = time;
68 self.owner.switchweapon = w_getbestweapon(self.owner);
74 void W_Mine_RemoteExplode()
76 if(self.owner.deadflag == DEAD_NO)
77 if(self.owner.lastmine)
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
84 W_Mine_DoRemoteExplode();
89 void W_Mine_ProximityExplode()
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"))
95 head = findradius(self.origin, cvar("g_balance_minelayer_radius"));
98 if(head == self.owner || !IsDifferentTeam(head, self.owner))
108 void W_Mine_Think (void)
112 self.nextthink = time;
116 self.projectiledeathtype |= HITTYPE_BOUNCE;
121 // set the mine for detonation when a foe gets too close
122 head = findradius(self.origin, cvar("g_balance_minelayer_detectionradius"));
125 if(head.classname == "player" && head.deadflag == DEAD_NO)
126 if(head != self.owner && IsDifferentTeam(head, self.owner)) // don't trigger for team mates
129 spamsound (self, CHAN_PROJECTILE, "weapons/mine_trigger.wav", VOL_BASE, ATTN_NORM);
130 self.mine_time = time + cvar("g_balance_minelayer_time");
135 // explode if it's time to
136 if(self.mine_time && time >= self.mine_time)
137 W_Mine_ProximityExplode();
140 if (self.owner.weapon == WEP_MINE_LAYER)
141 if (self.owner.deadflag == DEAD_NO)
142 if (self.minelayer_detonate)
143 W_Mine_RemoteExplode();
145 if(self.csqcprojectile_clientanimate == 0)
146 UpdateCSQCProjectile(self);
149 void W_Mine_Touch (void)
152 spamsound (self, CHAN_PROJECTILE, "weapons/mine_stick.wav", VOL_BASE, ATTN_NORM);
153 self.movetype = MOVETYPE_NONE; // lock the mine in place
154 // TODO: make sure this doesn't cause the mine to get stuck in the air if it falls over a moving entity
157 void W_Mine_Damage (entity inflictor, entity attacker, float damage, float deathtype, vector hitloc, vector force)
159 if (self.health <= 0)
161 self.health = self.health - damage;
162 self.angles = vectoangles(self.velocity);
163 if (self.health <= 0)
164 W_PrepareExplosionByDamage(attacker, W_Mine_Explode);
167 void W_Mine_Attack (void)
172 // scan how many mines we placed, and return if we reached our limit
173 if(cvar("g_balance_minelayer_limit"))
176 self.mine_number = 0;
177 for(mine = world; (mine = find(mine, classname, "mine")); ) if(mine.owner == self)
178 self.mine_number += 1;
180 if(self.mine_number >= cvar("g_balance_minelayer_limit"))
182 // the refire delay keeps this message from being spammed
183 sprint(self, strcat("You cannot place more than ^2", cvar_string("g_balance_minelayer_limit"), " ^7mines at a time\n") );
184 play2(self, "weapons/unavailable.wav");
189 if not(self.items & IT_UNLIMITED_WEAPON_AMMO)
190 self.ammo_rockets = self.ammo_rockets - cvar("g_balance_minelayer_ammo");
192 W_SetupShot_ProjectileSize (self, '-4 -4 -4', '4 4 4', FALSE, 5, "weapons/mine_fire.wav", cvar("g_balance_minelayer_damage"));
193 pointparticles(particleeffectnum("rocketlauncher_muzzleflash"), w_shotorg, w_shotdir * 1000, 1);
195 mine = WarpZone_RefSys_SpawnSameRefSys(self);
197 self.lastmine = mine;
198 if(cvar("g_balance_minelayer_detonatedelay") >= 0)
199 mine.spawnshieldtime = time + cvar("g_balance_minelayer_detonatedelay");
201 mine.spawnshieldtime = -1;
202 mine.classname = "mine";
203 mine.bot_dodge = TRUE;
204 mine.bot_dodgerating = cvar("g_balance_minelayer_damage") * 2; // * 2 because it can detonate inflight which makes it even more dangerous
206 mine.takedamage = DAMAGE_YES;
207 mine.damageforcescale = cvar("g_balance_minelayer_damageforcescale");
208 mine.health = cvar("g_balance_minelayer_health");
209 mine.event_damage = W_Mine_Damage;
211 mine.movetype = MOVETYPE_TOSS;
212 PROJECTILE_MAKETRIGGER(mine);
213 mine.projectiledeathtype = WEP_MINE_LAYER;
214 setsize (mine, '-4 -4 -4', '4 4 4'); // give it some size so it can be shot
216 setorigin (mine, w_shotorg - v_forward * 4); // move it back so it hits the wall at the right point
217 W_SetupProjectileVelocity(mine, cvar("g_balance_minelayer_speedstart"), 0);
218 mine.angles = vectoangles (mine.velocity);
220 mine.touch = W_Mine_Touch;
221 mine.think = W_Mine_Think;
222 mine.nextthink = time;
223 mine.cnt = time + cvar("g_balance_minelayer_lifetime");
224 mine.flags = FL_PROJECTILE;
226 CSQCProjectile(mine, FALSE, PROJECTILE_MINE, TRUE);
228 // muzzle flash for 1st person view
230 setmodel (flash, "models/flash.md3"); // precision set below
231 SUB_SetFade (flash, time, 0.1);
232 flash.effects = EF_ADDITIVE | EF_FULLBRIGHT | EF_LOWPRECISION;
233 W_AttachToShotorg(flash, '5 0 0');
238 void spawnfunc_weapon_minelayer (void); // defined in t_items.qc
240 float w_minelayer(float req)
246 // aim and decide to fire if appropriate
247 self.BUTTON_ATCK = bot_aim(cvar("g_balance_minelayer_speed"), 0, cvar("g_balance_minelayer_lifetime"), FALSE);
248 if(skill >= 2) // skill 0 and 1 bots won't detonate mines!
250 // decide whether to detonate mines
251 local entity mine, targetlist, targ;
252 local float edgedamage, coredamage, edgeradius, recipricoledgeradius, d;
253 local float selfdamage, teamdamage, enemydamage;
254 edgedamage = cvar("g_balance_minelayer_edgedamage");
255 coredamage = cvar("g_balance_minelayer_damage");
256 edgeradius = cvar("g_balance_minelayer_radius");
257 recipricoledgeradius = 1 / edgeradius;
261 targetlist = findchainfloat(bot_attack, TRUE);
262 mine = find(world, classname, "mine");
265 if (mine.owner != self)
267 mine = find(mine, classname, "mine");
273 d = vlen(targ.origin + (targ.mins + targ.maxs) * 0.5 - mine.origin);
274 d = bound(0, edgedamage + (coredamage - edgedamage) * sqrt(1 - d * recipricoledgeradius), 10000);
275 // count potential damage according to type of target
277 selfdamage = selfdamage + d;
278 else if (targ.team == self.team && teams_matter)
279 teamdamage = teamdamage + d;
280 else if (bot_shouldattack(targ))
281 enemydamage = enemydamage + d;
284 mine = find(mine, classname, "mine");
286 local float desirabledamage;
287 desirabledamage = enemydamage;
288 if (teamplay != 1 && time > self.invincible_finished && time > self.spawnshieldtime)
289 desirabledamage = desirabledamage - selfdamage * cvar("g_balance_selfdamagepercent");
290 if (self.team && teamplay != 1)
291 desirabledamage = desirabledamage - teamdamage;
293 mine = find(world, classname, "mine");
296 if (mine.owner != self)
298 mine = find(mine, classname, "mine");
301 makevectors(mine.v_angle);
303 if (skill > 9) // normal players only do this for the target they are tracking
309 (v_forward * normalize(mine.origin - targ.origin)< 0.1)
310 && desirabledamage > 0.1*coredamage
311 )self.BUTTON_ATCK2 = TRUE;
315 local float distance; distance= bound(300,vlen(self.origin-self.enemy.origin),30000);
316 //As the distance gets larger, a correct detonation gets near imposible
317 //Bots are assumed to use the mine spawnfunc_light to see if the mine gets near a player
318 if(v_forward * normalize(mine.origin - self.enemy.origin)< 0.1)
319 if(self.enemy.classname == "player")
320 if(desirabledamage >= 0.1*coredamage)
321 if(random()/distance*300 > frametime*bound(0,(10-skill)*0.2,1))
322 self.BUTTON_ATCK2 = TRUE;
323 // dprint(ftos(random()/distance*300),">");dprint(ftos(frametime*bound(0,(10-skill)*0.2,1)),"\n");
326 mine = find(mine, classname, "mine");
328 // if we would be doing at X percent of the core damage, detonate it
329 // but don't fire a new shot at the same time!
330 if (desirabledamage >= 0.75 * coredamage) //this should do group damage in rare fortunate events
331 self.BUTTON_ATCK2 = TRUE;
332 if ((skill > 6.5) && (selfdamage > self.health))
333 self.BUTTON_ATCK2 = FALSE;
334 //if(self.BUTTON_ATCK2 == TRUE)
335 // dprint(ftos(desirabledamage),"\n");
336 if (self.BUTTON_ATCK2 == TRUE) self.BUTTON_ATCK = FALSE;
339 else if (req == WR_THINK)
341 if (self.BUTTON_ATCK)
343 if(weapon_prepareattack(0, cvar("g_balance_minelayer_refire")))
346 weapon_thinkf(WFRAME_FIRE1, cvar("g_balance_minelayer_animtime"), w_ready);
350 if (self.BUTTON_ATCK2)
353 for(mine = world; (mine = find(mine, classname, "mine")); ) if(mine.owner == self)
355 if(!mine.minelayer_detonate)
357 mine.minelayer_detonate = TRUE;
362 sound (self, CHAN_WEAPON2, "weapons/mine_det.wav", VOL_BASE, ATTN_NORM);
365 else if (req == WR_PRECACHE)
367 precache_model ("models/flash.md3");
368 precache_model ("models/weapons/g_minelayer.md3");
369 precache_model ("models/weapons/v_minelayer.md3");
370 precache_model ("models/weapons/h_minelayer.iqm");
371 precache_sound ("weapons/mine_det.wav");
372 precache_sound ("weapons/mine_fire.wav");
373 precache_sound ("weapons/mine_stick.wav");
374 precache_sound ("weapons/mine_trigger.wav");
376 else if (req == WR_SETUP)
378 weapon_setup(WEP_MINE_LAYER);
380 else if (req == WR_CHECKAMMO1)
382 // don't switch while placing a mine
383 if ((ATTACK_FINISHED(self) <= time || self.weapon != WEP_MINE_LAYER)
384 && self.ammo_rockets < cvar("g_balance_minelayer_ammo"))
387 else if (req == WR_CHECKAMMO2)
393 float w_minelayer(float req)
395 if(req == WR_IMPACTEFFECT)
398 org2 = w_org + w_backoff * 12;
399 pointparticles(particleeffectnum("rocket_explode"), org2, '0 0 0', 1);
401 sound(self, CHAN_PROJECTILE, "weapons/mine_exp.wav", VOL_BASE, ATTN_NORM);
403 else if(req == WR_PRECACHE)
405 precache_sound("weapons/mine_exp.wav");
407 else if (req == WR_SUICIDEMESSAGE)
408 w_deathtypestring = "%s exploded";
409 else if (req == WR_KILLMESSAGE)
411 if(w_deathtype & HITTYPE_BOUNCE) // (remote detonation)
412 w_deathtypestring = "%s got too close to %s's mine";
413 else if(w_deathtype & HITTYPE_SPLASH)
414 w_deathtypestring = "%s almost dodged %s's mine";
416 w_deathtypestring = "%s stepped on %s's mine";