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.
10 REGISTER_WEAPON(MINE_LAYER, w_minelayer, IT_ROCKETS, 4, WEP_FLAG_NORMAL | WEP_TYPE_SPLASH, BOT_PICKUP_RATING_HIGH, "minelayer", "minelayer", "Mine Layer");
13 .float minelayer_detonate;
14 .float mine_number, mine_time;
16 void spawnfunc_weapon_minelayer (void)
18 weapon_defaultspawnfunc(WEP_MINE_LAYER);
21 void W_Mine_Unregister()
23 if(self.owner && self.owner.lastmine == self)
24 self.owner.lastmine = world;
27 void W_Mine_Explode ()
31 if(other.takedamage == DAMAGE_AIM)
32 if(other.classname == "player")
33 if(IsDifferentTeam(self.owner, other))
35 AnnounceTo(self.owner, "airshot");
37 self.event_damage = SUB_Null;
38 self.takedamage = DAMAGE_NO;
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);
42 if (self.owner.weapon == WEP_MINE_LAYER)
44 if(self.owner.ammo_rockets < cvar("g_balance_minelayer_ammo"))
46 self.owner.cnt = WEP_MINE_LAYER;
47 ATTACK_FINISHED(self.owner) = time;
48 self.owner.switchweapon = w_getbestweapon(self.owner);
54 void W_Mine_DoRemoteExplode ()
58 self.event_damage = SUB_Null;
59 self.takedamage = DAMAGE_NO;
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);
63 if (self.owner.weapon == WEP_MINE_LAYER)
65 if(self.owner.ammo_rockets < cvar("g_balance_minelayer_ammo"))
67 self.owner.cnt = WEP_MINE_LAYER;
68 ATTACK_FINISHED(self.owner) = time;
69 self.owner.switchweapon = w_getbestweapon(self.owner);
75 void W_Mine_RemoteExplode()
77 if(self.owner.deadflag == DEAD_NO)
78 if(self.owner.lastmine)
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
85 W_Mine_DoRemoteExplode();
90 void W_Mine_Think (void)
92 self.nextthink = time;
96 self.projectiledeathtype |= HITTYPE_BOUNCE;
101 // detect players who are close the mine and explode if the player should detonate it
103 head = findradius(self.origin, cvar("g_balance_minelayer_detectionradius"));
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
112 spamsound (self, CHAN_PROJECTILE, "weapons/mine_trigger.wav", VOL_BASE, ATTN_NORM);
113 self.mine_time = time + cvar("g_balance_minelayer_time");
118 // explode if it's time
119 if(self.mine_time && time >= self.mine_time)
126 if (self.owner.weapon == WEP_MINE_LAYER)
127 if (self.owner.deadflag == DEAD_NO)
128 if (self.minelayer_detonate)
129 W_Mine_RemoteExplode();
131 if(self.csqcprojectile_clientanimate == 0)
132 UpdateCSQCProjectile(self);
135 void W_Mine_Touch (void)
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
143 void W_Mine_Damage (entity inflictor, entity attacker, float damage, float deathtype, vector hitloc, vector force)
145 if (self.health <= 0)
147 self.health = self.health - damage;
148 self.angles = vectoangles(self.velocity);
149 if (self.health <= 0)
150 W_PrepareExplosionByDamage(attacker, W_Mine_Explode);
153 void W_Mine_Attack (void)
158 // scan how many mines we placed, and return if we reached our limit
159 if(cvar("g_balance_minelayer_limit"))
162 self.mine_number = 0;
163 for(mine = world; (mine = find(mine, classname, "mine")); ) if(mine.owner == self)
164 self.mine_number += 1;
166 if(self.mine_number >= cvar("g_balance_minelayer_limit"))
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");
175 if not(self.items & IT_UNLIMITED_WEAPON_AMMO)
176 self.ammo_rockets = self.ammo_rockets - cvar("g_balance_minelayer_ammo");
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);
181 mine = WarpZone_RefSys_SpawnSameRefSys(self);
183 self.lastmine = mine;
184 if(cvar("g_balance_minelayer_detonatedelay") >= 0)
185 mine.spawnshieldtime = time + cvar("g_balance_minelayer_detonatedelay");
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
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;
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
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);
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;
212 CSQCProjectile(mine, FALSE, PROJECTILE_MINE, TRUE);
214 // muzzle flash for 1st person view
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');
224 void spawnfunc_weapon_minelayer (void); // defined in t_items.qc
226 float w_minelayer(float req)
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!
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;
247 targetlist = findchainfloat(bot_attack, TRUE);
248 mine = find(world, classname, "mine");
251 if (mine.owner != self)
253 mine = find(mine, classname, "mine");
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
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;
270 mine = find(mine, classname, "mine");
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;
279 mine = find(world, classname, "mine");
282 if (mine.owner != self)
284 mine = find(mine, classname, "mine");
287 makevectors(mine.v_angle);
289 if (skill > 9) // normal players only do this for the target they are tracking
295 (v_forward * normalize(mine.origin - targ.origin)< 0.1)
296 && desirabledamage > 0.1*coredamage
297 )self.BUTTON_ATCK2 = TRUE;
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");
312 mine = find(mine, classname, "mine");
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;
325 else if (req == WR_THINK)
327 if (self.BUTTON_ATCK)
329 if(weapon_prepareattack(0, cvar("g_balance_minelayer_refire")))
332 weapon_thinkf(WFRAME_FIRE1, cvar("g_balance_minelayer_animtime"), w_ready);
336 if (self.BUTTON_ATCK2)
339 for(mine = world; (mine = find(mine, classname, "mine")); ) if(mine.owner == self)
341 if(!mine.minelayer_detonate)
343 mine.minelayer_detonate = TRUE;
348 sound (self, CHAN_WEAPON2, "weapons/mine_det.wav", VOL_BASE, ATTN_NORM);
351 else if (req == WR_PRECACHE)
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");
362 else if (req == WR_SETUP)
364 weapon_setup(WEP_MINE_LAYER);
366 else if (req == WR_CHECKAMMO1)
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"))
373 else if (req == WR_CHECKAMMO2)
379 float w_minelayer(float req)
381 if(req == WR_IMPACTEFFECT)
384 org2 = w_org + w_backoff * 12;
385 pointparticles(particleeffectnum("rocket_explode"), org2, '0 0 0', 1);
387 sound(self, CHAN_PROJECTILE, "weapons/mine_exp.wav", VOL_BASE, ATTN_NORM);
389 else if(req == WR_PRECACHE)
391 precache_sound("weapons/mine_exp.wav");
393 else if (req == WR_SUICIDEMESSAGE)
394 w_deathtypestring = "%s exploded";
395 else if (req == WR_KILLMESSAGE)
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";
402 w_deathtypestring = "%s stepped on %s's mine";