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 - Bot code for the weapon may be needed. The bot AI may not have any info about this gun yet.
4 - 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.
8 REGISTER_WEAPON(MINE_LAYER, w_minelayer, IT_ROCKETS, 4, WEP_FLAG_NORMAL | WEP_TYPE_SPLASH, BOT_PICKUP_RATING_HIGH, "minelayer", "minelayer", "Mine Layer");
11 .float minelayer_detonate;
12 .float mine_number, mine_time;
14 void spawnfunc_weapon_minelayer (void)
16 weapon_defaultspawnfunc(WEP_MINE_LAYER);
19 void W_Mine_Unregister()
21 if(self.owner && self.owner.lastmine == self)
22 self.owner.lastmine = world;
25 void W_Mine_Explode ()
29 if(other.takedamage == DAMAGE_AIM)
30 if(other.classname == "player")
31 if(IsDifferentTeam(self.owner, other))
33 AnnounceTo(self.owner, "airshot");
35 self.event_damage = SUB_Null;
36 self.takedamage = DAMAGE_NO;
38 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);
40 if (self.owner.weapon == WEP_MINE_LAYER)
42 if(self.owner.ammo_rockets < cvar("g_balance_minelayer_ammo"))
44 self.owner.cnt = WEP_MINE_LAYER;
45 ATTACK_FINISHED(self.owner) = time;
46 self.owner.switchweapon = w_getbestweapon(self.owner);
52 void W_Mine_DoRemoteExplode ()
56 self.event_damage = SUB_Null;
57 self.takedamage = DAMAGE_NO;
59 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);
61 if (self.owner.weapon == WEP_MINE_LAYER)
63 if(self.owner.ammo_rockets < cvar("g_balance_minelayer_ammo"))
65 self.owner.cnt = WEP_MINE_LAYER;
66 ATTACK_FINISHED(self.owner) = time;
67 self.owner.switchweapon = w_getbestweapon(self.owner);
73 void W_Mine_RemoteExplode()
75 if(self.owner.deadflag == DEAD_NO)
76 if(self.owner.lastmine)
78 if((self.spawnshieldtime >= 0)
79 ? (time >= self.spawnshieldtime) // timer
80 : (vlen(NearestPointOnBox(self.owner, self.origin) - self.origin) > cvar("g_balance_minelayer_radius")) // safety device
83 W_Mine_DoRemoteExplode();
88 void W_Mine_ProximityExplode()
90 // make sure no friend is in the mine's radius. If there is any, explosion is delayed until he's at a safe distance
91 if(cvar("g_balance_minelayer_protection"))
94 head = findradius(self.origin, cvar("g_balance_minelayer_radius"));
97 if(head == self.owner || !IsDifferentTeam(head, self.owner))
107 void W_Mine_Think (void)
111 self.nextthink = time;
115 self.projectiledeathtype |= HITTYPE_BOUNCE;
120 // set the mine for detonation when a foe gets too close
121 head = findradius(self.origin, cvar("g_balance_minelayer_detectionradius"));
124 if(head.classname == "player" && head.deadflag == DEAD_NO)
125 if(head != self.owner && IsDifferentTeam(head, self.owner)) // don't trigger for team mates
128 spamsound (self, CHAN_PROJECTILE, "weapons/mine_trigger.wav", VOL_BASE, ATTN_NORM);
129 self.mine_time = time + cvar("g_balance_minelayer_time");
134 // explode if it's time to
135 if(self.mine_time && time >= self.mine_time)
136 W_Mine_ProximityExplode();
139 if (self.owner.weapon == WEP_MINE_LAYER)
140 if (self.owner.deadflag == DEAD_NO)
141 if (self.minelayer_detonate)
142 W_Mine_RemoteExplode();
144 if(self.csqcprojectile_clientanimate == 0)
145 UpdateCSQCProjectile(self);
148 void W_Mine_Touch (void)
151 if(!other || (other.takedamage != DAMAGE_AIM && other.movetype == MOVETYPE_NONE))
153 spamsound (self, CHAN_PROJECTILE, "weapons/mine_stick.wav", VOL_BASE, ATTN_NORM);
154 self.movetype = MOVETYPE_NONE; // lock the mine in place
156 else if(self.movetype != MOVETYPE_NONE) // don't unstick a locked mine when someone touches it
157 self.velocity = '0 0 0';
160 void W_Mine_Damage (entity inflictor, entity attacker, float damage, float deathtype, vector hitloc, vector force)
162 if (self.health <= 0)
164 self.health = self.health - damage;
165 self.angles = vectoangles(self.velocity);
166 if (self.health <= 0)
167 W_PrepareExplosionByDamage(attacker, W_Mine_Explode);
170 void W_Mine_Attack (void)
175 // scan how many mines we placed, and return if we reached our limit
176 if(cvar("g_balance_minelayer_limit"))
179 self.mine_number = 0;
180 for(mine = world; (mine = find(mine, classname, "mine")); ) if(mine.owner == self)
181 self.mine_number += 1;
183 if(self.mine_number >= cvar("g_balance_minelayer_limit"))
185 // the refire delay keeps this message from being spammed
186 sprint(self, strcat("You cannot place more than ^2", cvar_string("g_balance_minelayer_limit"), " ^7mines at a time\n") );
187 play2(self, "weapons/unavailable.wav");
192 if not(self.items & IT_UNLIMITED_WEAPON_AMMO)
193 self.ammo_rockets = self.ammo_rockets - cvar("g_balance_minelayer_ammo");
195 W_SetupShot_ProjectileSize (self, '-4 -4 -4', '4 4 4', FALSE, 5, "weapons/mine_fire.wav", cvar("g_balance_minelayer_damage"));
196 pointparticles(particleeffectnum("rocketlauncher_muzzleflash"), w_shotorg, w_shotdir * 1000, 1);
198 mine = WarpZone_RefSys_SpawnSameRefSys(self);
200 self.lastmine = mine;
201 if(cvar("g_balance_minelayer_detonatedelay") >= 0)
202 mine.spawnshieldtime = time + cvar("g_balance_minelayer_detonatedelay");
204 mine.spawnshieldtime = -1;
205 mine.classname = "mine";
206 mine.bot_dodge = TRUE;
207 mine.bot_dodgerating = cvar("g_balance_minelayer_damage") * 2; // * 2 because it can detonate inflight which makes it even more dangerous
209 mine.takedamage = DAMAGE_YES;
210 mine.damageforcescale = cvar("g_balance_minelayer_damageforcescale");
211 mine.health = cvar("g_balance_minelayer_health");
212 mine.event_damage = W_Mine_Damage;
214 mine.movetype = MOVETYPE_TOSS;
215 PROJECTILE_MAKETRIGGER(mine);
216 mine.projectiledeathtype = WEP_MINE_LAYER;
217 setsize (mine, '-4 -4 -4', '4 4 4'); // give it some size so it can be shot
219 setorigin (mine, w_shotorg - v_forward * 4); // move it back so it hits the wall at the right point
220 W_SetupProjectileVelocity(mine, cvar("g_balance_minelayer_speedstart"), 0);
221 mine.angles = vectoangles (mine.velocity);
223 mine.touch = W_Mine_Touch;
224 mine.think = W_Mine_Think;
225 mine.nextthink = time;
226 mine.cnt = time + cvar("g_balance_minelayer_lifetime");
227 mine.flags = FL_PROJECTILE;
229 CSQCProjectile(mine, FALSE, PROJECTILE_MINE, TRUE);
231 // muzzle flash for 1st person view
233 setmodel (flash, "models/flash.md3"); // precision set below
234 SUB_SetFade (flash, time, 0.1);
235 flash.effects = EF_ADDITIVE | EF_FULLBRIGHT | EF_LOWPRECISION;
236 W_AttachToShotorg(flash, '5 0 0');
241 void spawnfunc_weapon_minelayer (void); // defined in t_items.qc
243 float w_minelayer(float req)
249 // aim and decide to fire if appropriate
250 self.BUTTON_ATCK = bot_aim(cvar("g_balance_minelayer_speed"), 0, cvar("g_balance_minelayer_lifetime"), FALSE);
251 if(skill >= 2) // skill 0 and 1 bots won't detonate mines!
253 // decide whether to detonate mines
254 local entity mine, targetlist, targ;
255 local float edgedamage, coredamage, edgeradius, recipricoledgeradius, d;
256 local float selfdamage, teamdamage, enemydamage;
257 edgedamage = cvar("g_balance_minelayer_edgedamage");
258 coredamage = cvar("g_balance_minelayer_damage");
259 edgeradius = cvar("g_balance_minelayer_radius");
260 recipricoledgeradius = 1 / edgeradius;
264 targetlist = findchainfloat(bot_attack, TRUE);
265 mine = find(world, classname, "mine");
268 if (mine.owner != self)
270 mine = find(mine, classname, "mine");
276 d = vlen(targ.origin + (targ.mins + targ.maxs) * 0.5 - mine.origin);
277 d = bound(0, edgedamage + (coredamage - edgedamage) * sqrt(1 - d * recipricoledgeradius), 10000);
278 // count potential damage according to type of target
280 selfdamage = selfdamage + d;
281 else if (targ.team == self.team && teams_matter)
282 teamdamage = teamdamage + d;
283 else if (bot_shouldattack(targ))
284 enemydamage = enemydamage + d;
287 mine = find(mine, classname, "mine");
289 local float desirabledamage;
290 desirabledamage = enemydamage;
291 if (teamplay != 1 && time > self.invincible_finished && time > self.spawnshieldtime)
292 desirabledamage = desirabledamage - selfdamage * cvar("g_balance_selfdamagepercent");
293 if (self.team && teamplay != 1)
294 desirabledamage = desirabledamage - teamdamage;
296 mine = find(world, classname, "mine");
299 if (mine.owner != self)
301 mine = find(mine, classname, "mine");
304 makevectors(mine.v_angle);
306 if (skill > 9) // normal players only do this for the target they are tracking
312 (v_forward * normalize(mine.origin - targ.origin)< 0.1)
313 && desirabledamage > 0.1*coredamage
314 )self.BUTTON_ATCK2 = TRUE;
318 local float distance; distance= bound(300,vlen(self.origin-self.enemy.origin),30000);
319 //As the distance gets larger, a correct detonation gets near imposible
320 //Bots are assumed to use the mine spawnfunc_light to see if the mine gets near a player
321 if(v_forward * normalize(mine.origin - self.enemy.origin)< 0.1)
322 if(self.enemy.classname == "player")
323 if(desirabledamage >= 0.1*coredamage)
324 if(random()/distance*300 > frametime*bound(0,(10-skill)*0.2,1))
325 self.BUTTON_ATCK2 = TRUE;
326 // dprint(ftos(random()/distance*300),">");dprint(ftos(frametime*bound(0,(10-skill)*0.2,1)),"\n");
329 mine = find(mine, classname, "mine");
331 // if we would be doing at X percent of the core damage, detonate it
332 // but don't fire a new shot at the same time!
333 if (desirabledamage >= 0.75 * coredamage) //this should do group damage in rare fortunate events
334 self.BUTTON_ATCK2 = TRUE;
335 if ((skill > 6.5) && (selfdamage > self.health))
336 self.BUTTON_ATCK2 = FALSE;
337 //if(self.BUTTON_ATCK2 == TRUE)
338 // dprint(ftos(desirabledamage),"\n");
339 if (self.BUTTON_ATCK2 == TRUE) self.BUTTON_ATCK = FALSE;
342 else if (req == WR_THINK)
344 if (self.BUTTON_ATCK)
346 if(weapon_prepareattack(0, cvar("g_balance_minelayer_refire")))
349 weapon_thinkf(WFRAME_FIRE1, cvar("g_balance_minelayer_animtime"), w_ready);
353 if (self.BUTTON_ATCK2)
356 for(mine = world; (mine = find(mine, classname, "mine")); ) if(mine.owner == self)
358 if(!mine.minelayer_detonate)
360 mine.minelayer_detonate = TRUE;
365 sound (self, CHAN_WEAPON2, "weapons/mine_det.wav", VOL_BASE, ATTN_NORM);
368 else if (req == WR_PRECACHE)
370 precache_model ("models/flash.md3");
371 precache_model ("models/weapons/g_minelayer.md3");
372 precache_model ("models/weapons/v_minelayer.md3");
373 precache_model ("models/weapons/h_minelayer.iqm");
374 precache_sound ("weapons/mine_det.wav");
375 precache_sound ("weapons/mine_fire.wav");
376 precache_sound ("weapons/mine_stick.wav");
377 precache_sound ("weapons/mine_trigger.wav");
379 else if (req == WR_SETUP)
381 weapon_setup(WEP_MINE_LAYER);
383 else if (req == WR_CHECKAMMO1)
385 // don't switch while placing a mine
386 if ((ATTACK_FINISHED(self) <= time || self.weapon != WEP_MINE_LAYER)
387 && self.ammo_rockets < cvar("g_balance_minelayer_ammo"))
390 else if (req == WR_CHECKAMMO2)
396 float w_minelayer(float req)
398 if(req == WR_IMPACTEFFECT)
401 org2 = w_org + w_backoff * 12;
402 pointparticles(particleeffectnum("rocket_explode"), org2, '0 0 0', 1);
404 sound(self, CHAN_PROJECTILE, "weapons/mine_exp.wav", VOL_BASE, ATTN_NORM);
406 else if(req == WR_PRECACHE)
408 precache_sound("weapons/mine_exp.wav");
410 else if (req == WR_SUICIDEMESSAGE)
411 w_deathtypestring = "%s exploded";
412 else if (req == WR_KILLMESSAGE)
414 if(w_deathtype & HITTYPE_BOUNCE) // (remote detonation)
415 w_deathtypestring = "%s got too close to %s's mine";
416 else if(w_deathtype & HITTYPE_SPLASH)
417 w_deathtypestring = "%s almost dodged %s's mine";
419 w_deathtypestring = "%s stepped on %s's mine";