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))
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)
138 W_Mine_ProximityExplode();
142 if (self.owner.weapon == WEP_MINE_LAYER)
143 if (self.owner.deadflag == DEAD_NO)
144 if (self.minelayer_detonate)
145 W_Mine_RemoteExplode();
147 if(self.csqcprojectile_clientanimate == 0)
148 UpdateCSQCProjectile(self);
151 void W_Mine_Touch (void)
154 spamsound (self, CHAN_PROJECTILE, "weapons/mine_stick.wav", VOL_BASE, ATTN_NORM);
155 self.movetype = MOVETYPE_NONE; // lock the mine in place
156 // TODO: make sure this doesn't cause the mine to get stuck in the air if it falls over a moving entity
159 void W_Mine_Damage (entity inflictor, entity attacker, float damage, float deathtype, vector hitloc, vector force)
161 if (self.health <= 0)
163 self.health = self.health - damage;
164 self.angles = vectoangles(self.velocity);
165 if (self.health <= 0)
166 W_PrepareExplosionByDamage(attacker, W_Mine_Explode);
169 void W_Mine_Attack (void)
174 // scan how many mines we placed, and return if we reached our limit
175 if(cvar("g_balance_minelayer_limit"))
178 self.mine_number = 0;
179 for(mine = world; (mine = find(mine, classname, "mine")); ) if(mine.owner == self)
180 self.mine_number += 1;
182 if(self.mine_number >= cvar("g_balance_minelayer_limit"))
184 // the refire delay keeps this message from being spammed
185 sprint(self, strcat("You cannot place more than ^2", cvar_string("g_balance_minelayer_limit"), " ^7mines at a time\n") );
186 play2(self, "weapons/unavailable.wav");
191 if not(self.items & IT_UNLIMITED_WEAPON_AMMO)
192 self.ammo_rockets = self.ammo_rockets - cvar("g_balance_minelayer_ammo");
194 W_SetupShot_ProjectileSize (self, '-4 -4 -4', '4 4 4', FALSE, 5, "weapons/mine_fire.wav", cvar("g_balance_minelayer_damage"));
195 pointparticles(particleeffectnum("rocketlauncher_muzzleflash"), w_shotorg, w_shotdir * 1000, 1);
197 mine = WarpZone_RefSys_SpawnSameRefSys(self);
199 self.lastmine = mine;
200 if(cvar("g_balance_minelayer_detonatedelay") >= 0)
201 mine.spawnshieldtime = time + cvar("g_balance_minelayer_detonatedelay");
203 mine.spawnshieldtime = -1;
204 mine.classname = "mine";
205 mine.bot_dodge = TRUE;
206 mine.bot_dodgerating = cvar("g_balance_minelayer_damage") * 2; // * 2 because it can detonate inflight which makes it even more dangerous
208 mine.takedamage = DAMAGE_YES;
209 mine.damageforcescale = cvar("g_balance_minelayer_damageforcescale");
210 mine.health = cvar("g_balance_minelayer_health");
211 mine.event_damage = W_Mine_Damage;
213 mine.movetype = MOVETYPE_TOSS;
214 PROJECTILE_MAKETRIGGER(mine);
215 mine.projectiledeathtype = WEP_MINE_LAYER;
216 setsize (mine, '-4 -4 -4', '4 4 4'); // give it some size so it can be shot
218 setorigin (mine, w_shotorg - v_forward * 4); // move it back so it hits the wall at the right point
219 W_SetupProjectileVelocity(mine, cvar("g_balance_minelayer_speedstart"), 0);
220 mine.angles = vectoangles (mine.velocity);
222 mine.touch = W_Mine_Touch;
223 mine.think = W_Mine_Think;
224 mine.nextthink = time;
225 mine.cnt = time + cvar("g_balance_minelayer_lifetime");
226 mine.flags = FL_PROJECTILE;
228 CSQCProjectile(mine, FALSE, PROJECTILE_MINE, TRUE);
230 // muzzle flash for 1st person view
232 setmodel (flash, "models/flash.md3"); // precision set below
233 SUB_SetFade (flash, time, 0.1);
234 flash.effects = EF_ADDITIVE | EF_FULLBRIGHT | EF_LOWPRECISION;
235 W_AttachToShotorg(flash, '5 0 0');
240 void spawnfunc_weapon_minelayer (void); // defined in t_items.qc
242 float w_minelayer(float req)
248 // aim and decide to fire if appropriate
249 self.BUTTON_ATCK = bot_aim(cvar("g_balance_minelayer_speed"), 0, cvar("g_balance_minelayer_lifetime"), FALSE);
250 if(skill >= 2) // skill 0 and 1 bots won't detonate mines!
252 // decide whether to detonate mines
253 local entity mine, targetlist, targ;
254 local float edgedamage, coredamage, edgeradius, recipricoledgeradius, d;
255 local float selfdamage, teamdamage, enemydamage;
256 edgedamage = cvar("g_balance_minelayer_edgedamage");
257 coredamage = cvar("g_balance_minelayer_damage");
258 edgeradius = cvar("g_balance_minelayer_radius");
259 recipricoledgeradius = 1 / edgeradius;
263 targetlist = findchainfloat(bot_attack, TRUE);
264 mine = find(world, classname, "mine");
267 if (mine.owner != self)
269 mine = find(mine, classname, "mine");
275 d = vlen(targ.origin + (targ.mins + targ.maxs) * 0.5 - mine.origin);
276 d = bound(0, edgedamage + (coredamage - edgedamage) * sqrt(1 - d * recipricoledgeradius), 10000);
277 // count potential damage according to type of target
279 selfdamage = selfdamage + d;
280 else if (targ.team == self.team && teams_matter)
281 teamdamage = teamdamage + d;
282 else if (bot_shouldattack(targ))
283 enemydamage = enemydamage + d;
286 mine = find(mine, classname, "mine");
288 local float desirabledamage;
289 desirabledamage = enemydamage;
290 if (teamplay != 1 && time > self.invincible_finished && time > self.spawnshieldtime)
291 desirabledamage = desirabledamage - selfdamage * cvar("g_balance_selfdamagepercent");
292 if (self.team && teamplay != 1)
293 desirabledamage = desirabledamage - teamdamage;
295 mine = find(world, classname, "mine");
298 if (mine.owner != self)
300 mine = find(mine, classname, "mine");
303 makevectors(mine.v_angle);
305 if (skill > 9) // normal players only do this for the target they are tracking
311 (v_forward * normalize(mine.origin - targ.origin)< 0.1)
312 && desirabledamage > 0.1*coredamage
313 )self.BUTTON_ATCK2 = TRUE;
317 local float distance; distance= bound(300,vlen(self.origin-self.enemy.origin),30000);
318 //As the distance gets larger, a correct detonation gets near imposible
319 //Bots are assumed to use the mine spawnfunc_light to see if the mine gets near a player
320 if(v_forward * normalize(mine.origin - self.enemy.origin)< 0.1)
321 if(self.enemy.classname == "player")
322 if(desirabledamage >= 0.1*coredamage)
323 if(random()/distance*300 > frametime*bound(0,(10-skill)*0.2,1))
324 self.BUTTON_ATCK2 = TRUE;
325 // dprint(ftos(random()/distance*300),">");dprint(ftos(frametime*bound(0,(10-skill)*0.2,1)),"\n");
328 mine = find(mine, classname, "mine");
330 // if we would be doing at X percent of the core damage, detonate it
331 // but don't fire a new shot at the same time!
332 if (desirabledamage >= 0.75 * coredamage) //this should do group damage in rare fortunate events
333 self.BUTTON_ATCK2 = TRUE;
334 if ((skill > 6.5) && (selfdamage > self.health))
335 self.BUTTON_ATCK2 = FALSE;
336 //if(self.BUTTON_ATCK2 == TRUE)
337 // dprint(ftos(desirabledamage),"\n");
338 if (self.BUTTON_ATCK2 == TRUE) self.BUTTON_ATCK = FALSE;
341 else if (req == WR_THINK)
343 if (self.BUTTON_ATCK)
345 if(weapon_prepareattack(0, cvar("g_balance_minelayer_refire")))
348 weapon_thinkf(WFRAME_FIRE1, cvar("g_balance_minelayer_animtime"), w_ready);
352 if (self.BUTTON_ATCK2)
355 for(mine = world; (mine = find(mine, classname, "mine")); ) if(mine.owner == self)
357 if(!mine.minelayer_detonate)
359 mine.minelayer_detonate = TRUE;
364 sound (self, CHAN_WEAPON2, "weapons/mine_det.wav", VOL_BASE, ATTN_NORM);
367 else if (req == WR_PRECACHE)
369 precache_model ("models/flash.md3");
370 precache_model ("models/weapons/g_minelayer.md3");
371 precache_model ("models/weapons/v_minelayer.md3");
372 precache_model ("models/weapons/h_minelayer.iqm");
373 precache_sound ("weapons/mine_det.wav");
374 precache_sound ("weapons/mine_fire.wav");
375 precache_sound ("weapons/mine_stick.wav");
376 precache_sound ("weapons/mine_trigger.wav");
378 else if (req == WR_SETUP)
380 weapon_setup(WEP_MINE_LAYER);
382 else if (req == WR_CHECKAMMO1)
384 // don't switch while placing a mine
385 if ((ATTACK_FINISHED(self) <= time || self.weapon != WEP_MINE_LAYER)
386 && self.ammo_rockets < cvar("g_balance_minelayer_ammo"))
389 else if (req == WR_CHECKAMMO2)
395 float w_minelayer(float req)
397 if(req == WR_IMPACTEFFECT)
400 org2 = w_org + w_backoff * 12;
401 pointparticles(particleeffectnum("rocket_explode"), org2, '0 0 0', 1);
403 sound(self, CHAN_PROJECTILE, "weapons/mine_exp.wav", VOL_BASE, ATTN_NORM);
405 else if(req == WR_PRECACHE)
407 precache_sound("weapons/mine_exp.wav");
409 else if (req == WR_SUICIDEMESSAGE)
410 w_deathtypestring = "%s exploded";
411 else if (req == WR_KILLMESSAGE)
413 if(w_deathtype & HITTYPE_BOUNCE) // (remote detonation)
414 w_deathtypestring = "%s got too close to %s's mine";
415 else if(w_deathtype & HITTYPE_SPLASH)
416 w_deathtypestring = "%s almost dodged %s's mine";
418 w_deathtypestring = "%s stepped on %s's mine";