diff --git a/progs/fte-server.src b/progs/fte-server.src index 342aa7b..e2ed557 100644 --- a/progs/fte-server.src +++ b/progs/fte-server.src @@ -25,6 +25,7 @@ ../source/server/entities/func.qc ../source/server/entities/traps.qc ../source/server/entities/lights.qc +../source/server/entities/mystery_box.qc ../source/server/entities/doors.qc ../source/server/entities/window.qc ../source/server/entities/machines.qc diff --git a/progs/standard.src b/progs/standard.src index 0f3395b..34a1863 100644 --- a/progs/standard.src +++ b/progs/standard.src @@ -29,6 +29,7 @@ ../source/server/entities/func.qc ../source/server/entities/traps.qc ../source/server/entities/lights.qc +../source/server/entities/mystery_box.qc ../source/server/entities/doors.qc ../source/server/entities/window.qc ../source/server/entities/machines.qc diff --git a/source/server/entities/machines.qc b/source/server/entities/machines.qc index 793edcd..0811ee5 100644 --- a/source/server/entities/machines.qc +++ b/source/server/entities/machines.qc @@ -1,9 +1,9 @@ /* server/entities/machines.qc - mbox, perks, pap + perks, pap, power - Copyright (C) 2021-2022 NZ:P Team + Copyright (C) 2021-2023 NZ:P Team This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License @@ -1083,844 +1083,6 @@ void() power_switch = isPowerOn = false; } -// -// -------------------- -// Mystery Box -// -------------------- -// - -#define MBOX_SPAWNFLAG_NOTHERE 1 -#define MBOX_SPAWNFLAG_NOLIGHT 2 - - -// -// MBOX_FreeEnt(ent) -// Marks an MBOX entity as able to be used. -// -void(entity ent) MBOX_FreeEnt = -{ - setmodel(ent, ""); - ent.classname = "freeMboxEntity"; - ent.touch = SUB_Null; - ent.think = SUB_Null; - -#ifdef FTE - ent.alpha = 1; -#endif -}; - -// -// MBOX_GetFreeEnt() -// Returns an MBOX entity to use. -// -entity() MBOX_GetFreeEnt = -{ - entity ent; - ent = find(world, classname, "freeMboxEntity"); - - if (ent == world) - error("MBOX_GetFreeEnt: No free MBOX Entity. (Hacks?)\n"); - - return ent; -}; - -void() updateBoxGlow -{ - if(self.goaldummy) - { - self.goaldummy.frame = self.frame; - } -}; - - -void() box_open1 =[ 1, box_open2 ] { self.frame = 1;updateBoxGlow(); Light_Yellow(self);}; -void() box_open2 =[ 2, box_open3 ] { self.frame = 2;updateBoxGlow();}; -void() box_open3 =[ 3, box_open4 ] { self.frame = 3;updateBoxGlow();}; -void() box_open4 =[ 4, box_open5 ] { self.frame = 4;updateBoxGlow();}; -void() box_open5 =[ 5, box_open6 ] { self.frame = 5;updateBoxGlow();}; -void() box_open6 =[ 6, box_open7 ] { self.frame = 6;updateBoxGlow();}; -void() box_open7 =[ 7, box_open8 ] { self.frame = 7;updateBoxGlow();}; -void() box_open8 =[ 8, SUB_Null ] { self.frame = 8;updateBoxGlow();}; - -void() resetbox = -{ - self.frame = 0; - self.boxstatus = 0; -}; - - -void() box_close1 =[ 9, box_close2 ] { self.frame = 9;updateBoxGlow();sound (self, CHAN_ITEM, "sounds/machines/mbox_close.wav", 1, ATTN_NORM); Light_None(self);}; -void() box_close2 =[ 10, box_close3 ] { self.frame = 10;updateBoxGlow();}; -void() box_close3 =[ 11, box_close4 ] { self.frame = 11;updateBoxGlow();}; -void() box_close4 =[ 12, resetbox ] { self.frame = 12;updateBoxGlow();}; - -// -// Getweaponid() -// Returns weapon ID from Mbox ID, as they are slightly different -// because.. reasons.. -// -float(float r) Getweaponid = -{ - switch(r) { - case 0: - return W_COLT; - case 1: - return W_KAR; - case 2: - return W_DB; - case 3: - return W_MG; - case 4: - return W_RAY; - case 5: - return W_THOMPSON; - case 6: - return W_M2; - case 7: - return W_PPSH; - case 8: - return W_SAWNOFF; - case 9: - return W_TESLA; - case 10: - return W_M1A1; - case 11: - return W_GEWEHR; - case 12: - return W_FG; - case 13: - return W_BROWNING; - case 14: - return W_KAR_SCOPE; - case 15: - return W_357; - case 16: - return W_STG; - case 17: - return W_PANZER; - case 18: - return W_BK; - case 19: - return W_PTRS; - case 20: - return W_MP40; - case 21: - return W_TRENCH; - case 22: - return W_BAR; - case 23: - return W_M1; - case 24: - return W_TYPE; - case 25: - return W_MP5K; - case 26: - return W_SPRING; - } - - return r; -} - -// -// randomweapon() -// Returns a Weapon if permitted by the map's MBOX data file. -// -float() randomweapon = -{ - float r; - r = rint((random() * 26)); - - // If this weapon is in our Box Array, we can return it. - if (BoxWeapons[r] == 1) { - return r; - } else { // It's not in the Array, try again until we find one. - return randomweapon(); - } -}; - -// -// CheckWeapon(w, user) -// Checks all 3 weapon slots to see if the Player is holding specified -// Weapon, ensures we do not give Duplicates. -// -float CheckWeapon (float w, entity user) = -{ - // Non-PaP Weapons - if (user.weapon == w || user.secondaryweapon == w || user.thirdweapon == w) - return 0; - - // PaP Weapons - if (EqualNonPapWeapon(user.weapon) == w || EqualNonPapWeapon(user.secondaryweapon) == w || EqualNonPapWeapon(user.thirdweapon) == w) - return 0; - - // We passed both, this weapon is okay - return 1; -}; - -// -// Reset_MBox() -// Resets the Mystery Box to it's inital State. -// -void() Reset_MBox = -{ - entity tempe; - self.velocity = '0 0 0'; - tempe = self; - self = self.owner; - box_close1(); - self = tempe; - self.owner.owner = world; - self.owner.boxstatus = 0; - MBOX_FreeEnt(self); -} - -// -// Float_Decreate() -// Make the Gun in the Box slowly descend, eventually -// resetting the Box. -// -void() Float_Decrease = -{ - makevectors(self.angles); - self.velocity = v_up*-5; - self.nextthink = time + 7; - self.think = Reset_MBox; -} - -// -// findboxspot() -// Locate a new MBox spot and turn this spot into -// a tp_spot. -// -void() findboxspot = -{ - local entity newspot; - local float box = rint(random(mystery_box_count)); - newspot = mystery_boxes[box]; - - // Ensure the spot we choose is valid. - while(newspot == world || newspot == self.owner) { - box = rint(random(mystery_box_count)); - newspot = mystery_boxes[box]; - } - - // Make our current spot a tp_spot - self.owner.model = "models/props/teddy.mdl"; - self.owner.frame = 2; - setmodel(self.owner, self.owner.model); - self.owner.classname = "mystery_box_tp_spot"; - self.owner.touch = SUB_Null; - self.owner.angles_y -= 90; - Light_None(self.owner); - - newspot.angles_y += 90; - - // Spawn the Box Glow if permitted - if (!(self.owner.spawnflags & MBOX_SPAWNFLAG_NOLIGHT)) - { - entity g; - g = MBOX_GetFreeEnt(); - g.classname = "mystery_glow"; - newspot.goaldummy = g; - setmodel(g,"models/machines/mglow$.mdl"); - setorigin(g,newspot.origin); - g.angles = newspot.angles; - -#ifdef FTE - g.alpha = 0.5; -#endif - } - - // Remove teddy - MBOX_FreeEnt(self); - - // Set some values and change the found Spot to an MBox - newspot.spins = 0; - newspot.boxstatus = 0; - newspot.touch = mystery_touch; - newspot.solid=SOLID_TRIGGER; - newspot.classname = "mystery"; - newspot.spawnflags = self.owner.spawnflags; - setorigin(newspot, newspot.origin); - setmodel (newspot, "models/machines/mystery.mdl"); - newspot.frame = 0; - setsize (newspot, VEC_HULL2_MIN, VEC_HULL2_MAX); -} - -// -// MBOX_MakeActive() -// Sets the Mystery Box touch to mystery_touch (delay) -// -void() MBOX_MakeActive = -{ - self.touch = mystery_touch; -} - -// -// MBOX_FindNewSpot() -// Locate a new location for the Mystery Box. -// -void() MBOX_FindNewSpot = -{ - entity new_box = world; - - // Find a new Mystery Box location - while(new_box == world || new_box == self) { - float box_index = rint(random(mystery_box_count)); - new_box = mystery_boxes[box_index]; - } - - // Stupid Teddy Bear being rotated!! - new_box.angles_y += 90; - - // Convert the spawn to a Mystery Box - new_box.spawnflags = self.spawnflags; - new_box.spins = new_box.boxstatus = 0; - new_box.think = MBOX_MakeActive; - new_box.nextthink = time + 2; - new_box.solid = SOLID_TRIGGER; - new_box.classname = "mystery"; - new_box.frame = 0; - setmodel(new_box, "models/machines/mystery.mdl"); - setsize(new_box, VEC_HULL2_MIN, VEC_HULL2_MAX); - - // Spawn the Box Glow if permitted - if (!(new_box.spawnflags & MBOX_SPAWNFLAG_NOLIGHT)) - { - entity light; - light = MBOX_GetFreeEnt(); - light.classname = "mystery_glow"; - new_box.goaldummy = light; - setmodel(light,"models/machines/mglow$.mdl"); - setorigin(light, new_box.origin); - light.angles = new_box.angles; - light.effects = EF_FULLBRIGHT; - -#ifdef FTE - - light.alpha = 0.5; - -#endif // FTE - } - - // Spawn some sparkles so it doesn't just "appear" - SpawnSpark(new_box.origin, 0.75); - // "Woosh" Effect - sound(new_box, CHAN_AUTO, "sounds/pu/drop.wav", 1, ATTN_NORM); -} - -// -// MBOX_StopFlying() -// We've done our dance, go away and -// delay respawning. -// -void() MBOX_StopFlying = -{ - // Spark Effect - SpawnSpark(self.origin, 0.75); - // "Woosh" Effect - sound(self, CHAN_AUTO, "sounds/pu/drop.wav", 1, ATTN_NORM); - // Disappear! - self.model = "models/props/teddy.mdl"; - setmodel(self, self.model); - self.frame = 2; - // Clean up - self.origin = self.oldvelocity; - self.angles = self.movement; - self.velocity = 0; - self.angles_y -= 90; - self.classname = "mystery_box_tp_spot"; - self.touch = SUB_Null; - // 12 Second delay to finding a new spot - self.think = MBOX_FindNewSpot; - self.nextthink = time + 12; -} - -// -// MBOX_AnimateFly() -// Rotate the Mystery Box as it flies away. -// -void() MBOX_AnimateFly = -{ - if (self.score == 0) - self.angles_x += 6; - else - self.angles_x -= 6; - - if (self.angles_x - self.movement_x >= 20) - self.score = 1; - else if (self.angles_x - self.movement_x <= -20) - self.score = 0; - - if(self.ltime < time) { - MBOX_StopFlying(); - } else { - self.nextthink = time + 0.05; - } -} - -// -// MBOX_FlyAway() -// The actual leave animation. -// -void() MBOX_FlyAway = -{ - // Begin rising - makevectors(self.angles); - self.movetype = MOVETYPE_NOCLIP; - self.velocity = v_up * 7; - // Animation lasts 4 seconds - self.ltime = time + 4; - // Animation Update - self.think = MBOX_AnimateFly; - self.nextthink = time + 0.05; -} - -// -// MBOX_Leave() -// Starts Mystery Box Leave animation. -// -void() MBOX_Leave = -{ - // Broadcast the laughter - sound(self, CHAN_ITEM, "sounds/pu/byebye.wav", 1, ATTN_NONE); - // "Woosh" Effect - sound(self, CHAN_AUTO, "sounds/pu/drop.wav", 1, ATTN_NORM); - // Spark Effect - SpawnSpark(self.origin, 0.75); - // Turn off Light & Glow - Light_None(self); - if (!(self.spawnflags & MBOX_SPAWNFLAG_NOLIGHT)) - if (self.goaldummy != world) - MBOX_FreeEnt(self.goaldummy); - // Save old position and angle - self.oldvelocity = self.origin; - self.movement = self.angles; - // Start flying - self.think = MBOX_FlyAway; - self.nextthink = time + 0.05; -} - -// -// MBOX_TeddyDespawn() -// Frees the Teddy Bear entity. -// -void() MBOX_TeddyDespawn = -{ - self.velocity = 0; - MBOX_FreeEnt(self); -} - -// -// MBOX_TeddyLeave() -// Make the Teddy Bear float away and start the -// timer for the Mystery Box to follow. -// -void() MBOX_TeddyLeave = -{ - // Fly slowly - self.velocity = v_up*75; - self.think = MBOX_TeddyDespawn; - self.nextthink = time + 5; - // Linger for 3 seconds, and then the Box follows. - self.owner.think = MBOX_Leave; - self.owner.nextthink = time + 3; -} - -// -// MBOX_PresentTeddy() -// Lets the Teddy Bear idle for a few seconds, -// return player points, kick-off leave. -// -void() MBOX_PresentTeddy = -{ - // Return the Player's points. - addmoney(self.owner.owner, 950, 0); - // Broadcast the bad luck. - sound(self, CHAN_ITEM, "sounds/misc/buy.wav", 1, ATTN_NONE); - sound(self, 2, "sounds/misc/giggle.wav", 1, ATTN_NONE); - // Linger for 2 seconds, and then fly away. - self.think = MBOX_TeddyLeave; - self.nextthink = time + 2; -} - -void() Float_Change = -{ - entity tpspot; - float r, tempf, teddygen; - string temps; - - tempf = randomweapon(); - r = Getweaponid(tempf); - while (!CheckWeapon (r, self.owner.owner)) - { - tempf = randomweapon(); - r = Getweaponid(tempf); - } - temps = GetWeaponModel(r, 1); - setmodel (self, temps); - - self.boxstatus = self.boxstatus + 0.01; - if (self.wait <= time) - { - tpspot = find(world, classname, "mystery_box_tp_spot"); - if (tpspot != world && self.owner.spins > 3) { - teddygen = random(); - //bprint(PRINT_HIGH, "found tp spot\n"); - } else { - teddygen = 0; - //bprint(PRINT_HIGH, "no tp spot or spins < 3\n"); - } - - self.velocity = '0 0 0'; - - if (!teddygen || teddygen < 0.7) { //teddy gen threshold, high means less chance - self.owner.boxstatus = 2; - self.weapon = r; - self.nextthink = time + 5; - self.think = Float_Decrease; - //bprint(PRINT_HIGH, "spot not found, or teddygun is > 0.7\n"); - return; - } - else { - addmoney(self.owner.owner, 950, 0); - self.model = "models/props/teddy.mdl"; - setmodel(self, self.model); - self.angles_y = self.angles_y - 90; - self.nextthink = time + 1; - self.think = MBOX_PresentTeddy; - ach_tracker_luck++; - - if (ach_tracker_luck >= 10) - GiveAchievement(11); - return; - } - } - if (self.ltime <= time) - { - self.velocity_z = self.velocity_z*0.5; - self.ltime = 0.5; - } - self.nextthink = time + self.boxstatus; - self.think = Float_Change; -} - -void() finish_mbox_setup = -{ - // Temporary hack for random box spawns until rewrite - Mikey (27/03/2023, DD/MM/YYYY) - if(self.spawnflags & MBOX_SPAWNFLAG_NOTHERE) { - entity temp = MBOX_GetFreeEnt(); - temp.classname = "mystery_helper"; - - temp.owner = self; - temp.think = findboxspot; - temp.nextthink = time + 0.1; - } else if(!(self.spawnflags & MBOX_SPAWNFLAG_NOLIGHT)) { - entity g = MBOX_GetFreeEnt(); - g.classname = "mystery_glow"; - - self.goaldummy = g; - setmodel(g,"models/machines/mglow$.mdl"); - setorigin(g,self.origin); - g.angles = self.angles; - -#ifdef FTE - g.alpha = 0.5; -#endif - } -} - -void() allocate_floating_weapons = -{ - self.think = SUB_Null; - - // Spawn all of our floating weapon entities - // Multiply by 2 to account for the glow. - for (float i = 0; i < mystery_box_count * 3; i++) { - entity tempe = spawn(); - tempe.classname = "freeMboxEntity"; - } - - finish_mbox_setup(); -} - -void() Create_Floating_Weapon = -{ - entity gun; - float r, tempf; - string temps; - - tempf = randomweapon(); - r = Getweaponid(tempf); - while (!CheckWeapon (r, self.owner.owner)) - { - tempf = randomweapon(); - r = Getweaponid(tempf); - } - temps = GetWeaponModel(r, 1); - - gun = MBOX_GetFreeEnt(); - gun.classname = "mystery_weapon"; - - setorigin (gun, self.origin); - setmodel (gun, temps); - setsize (gun, '0 0 0', '0 0 0'); - gun.angles = self.angles; - gun.effects = EF_FULLBRIGHT; - - gun.movetype = MOVETYPE_NOCLIP; - gun.solid = SOLID_NOT; - makevectors(self.angles); - gun.velocity = v_up*15; - - gun.owner = self; - self.boxweapon = gun; - - gun.ltime = time+2; - gun.boxstatus = 0.01; - gun.wait = time + 5; - gun.nextthink = time + 0.01; - gun.think = Float_Change; -} - -void() mystery_box_tp_spot = -{ - precache_model ("models/props/teddy.mdl"); - precache_sound ("sounds/pu/byebye.wav"); - precache_sound ("sounds/misc/giggle.wav"); - - self.solid=SOLID_TRIGGER; - self.classname = "mystery_box_tp_spot"; - setorigin(self, self.origin); - setmodel (self, "models/props/teddy.mdl"); - setsize (self, VEC_HULL2_MIN, VEC_HULL2_MAX); - - if (self.model == "models/props/teddy.mdl") { - self.frame = 2; - self.angles_y -= 90; - } - - mystery_boxes[mystery_box_count] = self; - mystery_box_count++; -}; - -void() mystery_touch = -{ - entity tempe; - - if (other.classname != "player" || other.downed) - return; - - if (!self.boxstatus) { - useprint (other, 6, 950, 0); - } - if (self.boxstatus == 2 && self.owner == other) { - useprint (other, 7, 0, 0); - } - - if (other.button7 && !other.semiuse) - { - other.semiuse = true; - if (!self.boxstatus) - { - if (other.points >= 950) - { - sound (self, CHAN_ITEM, "sounds/machines/mbox_open.wav", 1, ATTN_NORM); - addmoney(other, -950, FALSE); - self.boxstatus = 1; - self.owner = other; - box_open1 (); - Create_Floating_Weapon(); - self.spins++; - } - else { - centerprint (other, STR_NOTENOUGHPOINTS); - sound(other, 0, "sounds/misc/denybuy.wav", 1, 1); - } - } - if (self.boxstatus == 2) - { - if (self.owner == other) - { - other.reload_delay = 0; - self.boxstatus = 0; - self.owner = world; - if (other.weapon != self.boxweapon.weapon && other.secondaryweapon != self.boxweapon.weapon && other.secondaryweapon && other.thirdweapon != self.boxweapon.weapon) - { - if ((other.perks & P_MULE) && !other.thirdweapon) { - // store secondary weapon - local float tempf = other.secondaryweapon; - local float tempf1 = other.secondarymag; - local float tempf2 = other.secondaryammo; - local float tempf3 = other.secondarymag2; - // move primary to secondary - other.secondaryweapon = other.weapon; - other.secondarymag = other.currentmag; - other.secondarymag2 = other.currentmag2; - other.secondaryammo = other.currentammo; - // move secondary to tertiary - other.thirdweapon = tempf; - other.thirdmag = tempf1; - other.thirdammo = tempf2; - other.thirdmag2 = tempf3; - } - - // give boxweapon - other.weapon = self.boxweapon.weapon; - other.currentammo = getWeaponAmmo(self.boxweapon.weapon); - other.currentmag = getWeaponMag(self.boxweapon.weapon); - other.weaponskin = 0; - - if (other.weapon != W_KAR_SCOPE && other.weapon != W_HEADCRACKER && !IsDualWeapon(other.weapon)) { - other.weapon2model = ""; - } - -#ifndef FTE - - other.Flash_Offset = GetWeaponFlash_Offset(self.boxweapon.weapon); - other.Flash_Size = GetWeaponFlash_Size(self.boxweapon.weapon); - other.Weapon_Name = GetWeaponName(self.boxweapon.weapon); - -#endif // FTE - - } - else if (other.weapon == self.boxweapon.weapon) - { - other.currentammo = getWeaponAmmo(self.boxweapon.weapon); - other.currentmag = getWeaponMag(self.boxweapon.weapon); - } - else if (other.secondaryweapon == self.boxweapon.weapon) - { - other.secondaryammo = getWeaponAmmo(self.boxweapon.weapon); - other.secondarymag = getWeaponMag(self.boxweapon.weapon); - } - else if (other.thirdweapon == self.boxweapon.weapon) - { - other.thirdammo = getWeaponAmmo(self.boxweapon.weapon); - other.thirdmag = getWeaponMag(self.boxweapon.weapon); - } - else if (!other.secondaryweapon) - { - WeaponSwitch(other); - other.weapon = self.boxweapon.weapon; - other.currentammo = getWeaponAmmo(self.boxweapon.weapon); - other.currentmag = getWeaponMag(self.boxweapon.weapon); - other.weaponskin = GetWepSkin(self.boxweapon.weapon); - -#ifndef FTE - - other.Flash_Offset = GetWeaponFlash_Offset(self.boxweapon.weapon); - other.Flash_Size = GetWeaponFlash_Size(self.boxweapon.weapon); - other.Weapon_Name = GetWeaponName(self.boxweapon.weapon); - -#endif // FTE - - } - else if (!other.thirdweapon && (other.perks & P_MULE)) - { - - } - sound(self, 0,"sounds/misc/ching.wav", 1, 1); - tempe = self; - self = other; - - if (GetFrame(self.weapon, FIRST_TAKE_START) != 0) - Weapon_PlayViewModelAnimation(ANIM_FIRST_TAKE, SUB_Null, 0); - else - Weapon_PlayViewModelAnimation(ANIM_TAKE_OUT, SUB_Null, 0); - - SwitchWeapon(self.weapon); - self = tempe; - MBOX_FreeEnt(self.boxweapon); - box_close1(); - } - } - } -} - - -// -// Load_Mbox_Data() -// Opens the map's MBOX Data File and adds parsed data -// into the Box's weapon Array. -// ---------- -// TODO: Possibly investigate making this a little better and more modular -// so adding MBOX Weapons can be easier? -// -void() Load_Mbox_Data = -{ - float file; - string h; - int weapons_all_disabled = 1; - - // Attempt to Open the File - h = strcat(mapname, ".mbox"); - h = strcat("maps/", h); - file = fopen (h, FILE_READ); - - // There was no MBOX Data, enable all Weapons! - // R.I.P. PSP Memory 90% of the time in this case.. - if (file == -1) { - for (float i = 0; i < 27; i++) { - BoxWeapons[i] = 1; - } - fclose(file); - } else { - // Parse each Line and write the Data into our Array. - for (float i = 0; i < 27; i++) { - h = strtrim((fgets(file))); - BoxWeapons[i] = stof(h); - - if (stof(h) == 1) - weapons_all_disabled = 0; - } - } - - if (weapons_all_disabled) { - for (float i = 0; i < 27; i++) { - BoxWeapons[i] = 1; - } - } - - for(float i = 0; i < 27; i++) { - // Precache Weapon Data if enabled - if (BoxWeapons[i]) { - precache_model(GetWeaponModel(Getweaponid(i), 0)); - precache_model(GetWeaponModel(Getweaponid(i), 1)); - precache_extra(Getweaponid(i)); - } - } - - fclose(file); -} - -void() mystery_box = -{ - Load_Mbox_Data(); - - precache_model ("models/machines/mystery.mdl"); - precache_sound ("sounds/machines/mbox_open.wav"); - precache_sound ("sounds/machines/mbox_close.wav"); - - if (!(self.spawnflags & MBOX_SPAWNFLAG_NOLIGHT)) - precache_model ("models/machines/mglow$.mdl"); - - self.solid = SOLID_TRIGGER; - self.classname = "mystery"; - setorigin(self, self.origin); - setmodel (self, "models/machines/mystery.mdl"); - setsize (self, VEC_HULL2_MIN, VEC_HULL2_MAX); - - self.touch = mystery_touch; - boxOrigin = self.origin; - - mystery_boxes[mystery_box_count] = self; - mystery_box_count++; - - self.think = allocate_floating_weapons; - self.nextthink = time + 0.2; -} - // // -------------------- // Pack-a-Punch diff --git a/source/server/entities/mystery_box.qc b/source/server/entities/mystery_box.qc new file mode 100644 index 0000000..0f1a355 --- /dev/null +++ b/source/server/entities/mystery_box.qc @@ -0,0 +1,871 @@ +/* + server/entities/mystery_box.qc + + Mystery Box Entity Logic + + Copyright (C) 2021-2023 NZ:P Team + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2 + of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + + See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to: + + Free Software Foundation, Inc. + 59 Temple Place - Suite 330 + Boston, MA 02111-1307, USA + +*/ + +void(vector where, float time_alive) SpawnSpark; + +#define MBOX_SPAWNFLAG_NOTHERE 1 +#define MBOX_SPAWNFLAG_NOLIGHT 2 + + +// +// MBOX_FreeEnt(ent) +// Marks an MBOX entity as able to be used. +// +void(entity ent) MBOX_FreeEnt = +{ + setmodel(ent, ""); + ent.classname = "freeMboxEntity"; + ent.touch = SUB_Null; + ent.think = SUB_Null; + ent.effects = 0; + +#ifdef FTE + + ent.alpha = 1; + +#endif // FTE + +}; + +// +// MBOX_GetFreeEnt() +// Returns an MBOX entity to use. +// +entity() MBOX_GetFreeEnt = +{ + entity ent; + ent = find(world, classname, "freeMboxEntity"); + + if (ent == world) + error("MBOX_GetFreeEnt: No free MBOX Entity. (Hacks?)\n"); + + return ent; +}; + +void() updateBoxGlow +{ + if(self.goaldummy) + { + self.goaldummy.frame = self.frame; + } +}; + + +void() box_open1 =[ 1, box_open2 ] { self.frame = 1;updateBoxGlow(); Light_Yellow(self);}; +void() box_open2 =[ 2, box_open3 ] { self.frame = 2;updateBoxGlow();}; +void() box_open3 =[ 3, box_open4 ] { self.frame = 3;updateBoxGlow();}; +void() box_open4 =[ 4, box_open5 ] { self.frame = 4;updateBoxGlow();}; +void() box_open5 =[ 5, box_open6 ] { self.frame = 5;updateBoxGlow();}; +void() box_open6 =[ 6, box_open7 ] { self.frame = 6;updateBoxGlow();}; +void() box_open7 =[ 7, box_open8 ] { self.frame = 7;updateBoxGlow();}; +void() box_open8 =[ 8, SUB_Null ] { self.frame = 8;updateBoxGlow();}; + +void() resetbox = +{ + self.frame = 0; + self.boxstatus = 0; +}; + + +void() box_close1 =[ 9, box_close2 ] { self.frame = 9;updateBoxGlow();sound (self, CHAN_ITEM, "sounds/machines/mbox_close.wav", 1, ATTN_NORM); Light_None(self);}; +void() box_close2 =[ 10, box_close3 ] { self.frame = 10;updateBoxGlow();}; +void() box_close3 =[ 11, box_close4 ] { self.frame = 11;updateBoxGlow();}; +void() box_close4 =[ 12, resetbox ] { self.frame = 12;updateBoxGlow();}; + +// +// Getweaponid() +// Returns weapon ID from Mbox ID, as they are slightly different +// because.. reasons.. +// +float(float r) Getweaponid = +{ + switch(r) { + case 0: + return W_COLT; + case 1: + return W_KAR; + case 2: + return W_DB; + case 3: + return W_MG; + case 4: + return W_RAY; + case 5: + return W_THOMPSON; + case 6: + return W_M2; + case 7: + return W_PPSH; + case 8: + return W_SAWNOFF; + case 9: + return W_TESLA; + case 10: + return W_M1A1; + case 11: + return W_GEWEHR; + case 12: + return W_FG; + case 13: + return W_BROWNING; + case 14: + return W_KAR_SCOPE; + case 15: + return W_357; + case 16: + return W_STG; + case 17: + return W_PANZER; + case 18: + return W_BK; + case 19: + return W_PTRS; + case 20: + return W_MP40; + case 21: + return W_TRENCH; + case 22: + return W_BAR; + case 23: + return W_M1; + case 24: + return W_TYPE; + case 25: + return W_MP5K; + case 26: + return W_SPRING; + } + + return r; +} + +// +// randomweapon() +// Returns a Weapon if permitted by the map's MBOX data file. +// +float() randomweapon = +{ + float r; + r = rint((random() * 26)); + + // If this weapon is in our Box Array, we can return it. + if (BoxWeapons[r] == 1) { + return r; + } else { // It's not in the Array, try again until we find one. + return randomweapon(); + } +}; + +// +// CheckWeapon(w, user) +// Checks all 3 weapon slots to see if the Player is holding specified +// Weapon, ensures we do not give Duplicates. +// +float CheckWeapon (float w, entity user) = +{ + // Non-PaP Weapons + if (user.weapon == w || user.secondaryweapon == w || user.thirdweapon == w) + return 0; + + // PaP Weapons + if (EqualNonPapWeapon(user.weapon) == w || EqualNonPapWeapon(user.secondaryweapon) == w || EqualNonPapWeapon(user.thirdweapon) == w) + return 0; + + // We passed both, this weapon is okay + return 1; +}; + +// +// Reset_MBox() +// Resets the Mystery Box to it's inital State. +// +void() Reset_MBox = +{ + entity tempe; + self.velocity = '0 0 0'; + tempe = self; + self = self.owner; + box_close1(); + self = tempe; + self.owner.owner = world; + self.owner.boxstatus = 0; + MBOX_FreeEnt(self); +} + +// +// Float_Decreate() +// Make the Gun in the Box slowly descend, eventually +// resetting the Box. +// +void() Float_Decrease = +{ + makevectors(self.angles); + self.velocity = v_up*-5; + self.nextthink = time + 7; + self.think = Reset_MBox; +} + +// +// findboxspot() +// Locate a new MBox spot and turn this spot into +// a tp_spot. +// +void() findboxspot = +{ + local entity newspot; + local float box = rint(random(mystery_box_count)); + newspot = mystery_boxes[box]; + + // Ensure the spot we choose is valid. + while(newspot == world || newspot == self.owner) { + box = rint(random(mystery_box_count)); + newspot = mystery_boxes[box]; + } + + // Make our current spot a tp_spot + self.owner.model = "models/props/teddy.mdl"; + self.owner.frame = 2; + setmodel(self.owner, self.owner.model); + self.owner.classname = "mystery_box_tp_spot"; + self.owner.touch = SUB_Null; + self.owner.angles_y -= 90; + Light_None(self.owner); + + newspot.angles_y += 90; + + // Spawn the Box Glow if permitted + if (!(self.owner.spawnflags & MBOX_SPAWNFLAG_NOLIGHT)) + { + entity g; + g = MBOX_GetFreeEnt(); + g.classname = "mystery_glow"; + newspot.goaldummy = g; + setmodel(g,"models/machines/mglow$.mdl"); + setorigin(g,newspot.origin); + g.angles = newspot.angles; + +#ifdef FTE + + g.alpha = 0.5; + +#endif // FTE + + } + + // Remove teddy + MBOX_FreeEnt(self); + + // Set some values and change the found Spot to an MBox + newspot.spins = 0; + newspot.boxstatus = 0; + newspot.touch = mystery_touch; + newspot.solid=SOLID_TRIGGER; + newspot.classname = "mystery"; + newspot.spawnflags = self.owner.spawnflags; + setorigin(newspot, newspot.origin); + setmodel (newspot, "models/machines/mystery.mdl"); + newspot.frame = 0; + setsize (newspot, VEC_HULL2_MIN, VEC_HULL2_MAX); +} + +// +// MBOX_MakeActive() +// Sets the Mystery Box touch to mystery_touch (delay) +// +void() MBOX_MakeActive = +{ + self.touch = mystery_touch; +} + +// +// MBOX_FindNewSpot() +// Locate a new location for the Mystery Box. +// +void() MBOX_FindNewSpot = +{ + entity new_box = world; + + // Find a new Mystery Box location + while(new_box == world || new_box == self) { + float box_index = rint(random(mystery_box_count)); + new_box = mystery_boxes[box_index]; + } + + // Stupid Teddy Bear being rotated!! + new_box.angles_y += 90; + + // Convert the spawn to a Mystery Box + new_box.spawnflags = self.spawnflags; + new_box.spins = new_box.boxstatus = 0; + new_box.think = MBOX_MakeActive; + new_box.nextthink = time + 2; + new_box.solid = SOLID_TRIGGER; + new_box.classname = "mystery"; + new_box.frame = 0; + setmodel(new_box, "models/machines/mystery.mdl"); + setsize(new_box, VEC_HULL2_MIN, VEC_HULL2_MAX); + + // Spawn the Box Glow if permitted + if (!(new_box.spawnflags & MBOX_SPAWNFLAG_NOLIGHT)) + { + entity light; + light = MBOX_GetFreeEnt(); + light.classname = "mystery_glow"; + new_box.goaldummy = light; + setmodel(light,"models/machines/mglow$.mdl"); + setorigin(light, new_box.origin); + light.angles = new_box.angles; + light.effects = EF_FULLBRIGHT; + +#ifdef FTE + + light.alpha = 0.5; + +#endif // FTE + + } + + // Spawn some sparkles so it doesn't just "appear" + SpawnSpark(new_box.origin, 0.75); + // "Woosh" Effect + sound(new_box, CHAN_AUTO, "sounds/pu/drop.wav", 1, ATTN_NORM); +} + +// +// MBOX_StopFlying() +// We've done our dance, go away and +// delay respawning. +// +void() MBOX_StopFlying = +{ + // Spark Effect + SpawnSpark(self.origin, 0.75); + // "Woosh" Effect + sound(self, CHAN_AUTO, "sounds/pu/drop.wav", 1, ATTN_NORM); + // Disappear! + self.model = "models/props/teddy.mdl"; + setmodel(self, self.model); + self.frame = 2; + // Clean up + self.origin = self.oldvelocity; + self.angles = self.movement; + self.velocity = 0; + self.angles_y -= 90; + self.classname = "mystery_box_tp_spot"; + self.touch = SUB_Null; + // 12 Second delay to finding a new spot + self.think = MBOX_FindNewSpot; + self.nextthink = time + 12; +} + +// +// MBOX_AnimateFly() +// Rotate the Mystery Box as it flies away. +// +void() MBOX_AnimateFly = +{ + if (self.score == 0) + self.angles_x += 6; + else + self.angles_x -= 6; + + if (self.angles_x - self.movement_x >= 20) + self.score = 1; + else if (self.angles_x - self.movement_x <= -20) + self.score = 0; + + if(self.ltime < time) { + MBOX_StopFlying(); + } else { + self.nextthink = time + 0.05; + } +} + +// +// MBOX_FlyAway() +// The actual leave animation. +// +void() MBOX_FlyAway = +{ + // Begin rising + makevectors(self.angles); + self.movetype = MOVETYPE_NOCLIP; + self.velocity = v_up * 7; + // Animation lasts 4 seconds + self.ltime = time + 4; + // Animation Update + self.think = MBOX_AnimateFly; + self.nextthink = time + 0.05; +} + +// +// MBOX_Leave() +// Starts Mystery Box Leave animation. +// +void() MBOX_Leave = +{ + // Broadcast the laughter + sound(self, CHAN_ITEM, "sounds/pu/byebye.wav", 1, ATTN_NONE); + // "Woosh" Effect + sound(self, CHAN_AUTO, "sounds/pu/drop.wav", 1, ATTN_NORM); + // Spark Effect + SpawnSpark(self.origin, 0.75); + // Turn off Light & Glow + Light_None(self); + if (!(self.spawnflags & MBOX_SPAWNFLAG_NOLIGHT)) + if (self.goaldummy != world) + MBOX_FreeEnt(self.goaldummy); + // Save old position and angle + self.oldvelocity = self.origin; + self.movement = self.angles; + // Start flying + self.think = MBOX_FlyAway; + self.nextthink = time + 0.05; +} + +// +// MBOX_TeddyDespawn() +// Frees the Teddy Bear entity. +// +void() MBOX_TeddyDespawn = +{ + self.velocity = 0; + MBOX_FreeEnt(self); +} + +// +// MBOX_TeddyLeave() +// Make the Teddy Bear float away and start the +// timer for the Mystery Box to follow. +// +void() MBOX_TeddyLeave = +{ + // Fly slowly + self.velocity = v_up*75; + self.think = MBOX_TeddyDespawn; + self.nextthink = time + 5; + // Linger for 3 seconds, and then the Box follows. + self.owner.think = MBOX_Leave; + self.owner.nextthink = time + 3; +} + +// +// MBOX_PresentTeddy() +// Lets the Teddy Bear idle for a few seconds, +// return player points, kick-off leave. +// +void() MBOX_PresentTeddy = +{ + // Return the Player's points. + addmoney(self.owner.owner, 950, 0); + // Broadcast the bad luck. + sound(self, CHAN_ITEM, "sounds/misc/buy.wav", 1, ATTN_NONE); + sound(self, 2, "sounds/misc/giggle.wav", 1, ATTN_NONE); + // Linger for 2 seconds, and then fly away. + self.think = MBOX_TeddyLeave; + self.nextthink = time + 2; +} + +void() Float_Change = +{ + entity tpspot; + float r, tempf, teddygen; + string temps; + + tempf = randomweapon(); + r = Getweaponid(tempf); + while (!CheckWeapon (r, self.owner.owner)) + { + tempf = randomweapon(); + r = Getweaponid(tempf); + } + temps = GetWeaponModel(r, 1); + setmodel (self, temps); + + self.boxstatus = self.boxstatus + 0.01; + if (self.wait <= time) + { + tpspot = find(world, classname, "mystery_box_tp_spot"); + if (tpspot != world && self.owner.spins > 3) { + teddygen = random(); + //bprint(PRINT_HIGH, "found tp spot\n"); + } else { + teddygen = 0; + //bprint(PRINT_HIGH, "no tp spot or spins < 3\n"); + } + + self.velocity = '0 0 0'; + + if (!teddygen || teddygen < 0.7) { //teddy gen threshold, high means less chance + self.owner.boxstatus = 2; + self.weapon = r; + self.nextthink = time + 5; + self.think = Float_Decrease; + //bprint(PRINT_HIGH, "spot not found, or teddygun is > 0.7\n"); + return; + } + else { + addmoney(self.owner.owner, 950, 0); + self.model = "models/props/teddy.mdl"; + setmodel(self, self.model); + self.angles_y = self.angles_y - 90; + self.nextthink = time + 1; + self.think = MBOX_PresentTeddy; + ach_tracker_luck++; + + if (ach_tracker_luck >= 10) + GiveAchievement(11); + return; + } + } + if (self.ltime <= time) + { + self.velocity_z = self.velocity_z*0.5; + self.ltime = 0.5; + } + self.nextthink = time + self.boxstatus; + self.think = Float_Change; +} + +void() finish_mbox_setup = +{ + // Temporary hack for random box spawns until rewrite - Mikey (27/03/2023, DD/MM/YYYY) + if(self.spawnflags & MBOX_SPAWNFLAG_NOTHERE) { + entity temp = MBOX_GetFreeEnt(); + temp.classname = "mystery_helper"; + + temp.owner = self; + temp.think = findboxspot; + temp.nextthink = time + 0.1; + } else if(!(self.spawnflags & MBOX_SPAWNFLAG_NOLIGHT)) { + entity g = MBOX_GetFreeEnt(); + g.classname = "mystery_glow"; + + self.goaldummy = g; + setmodel(g,"models/machines/mglow$.mdl"); + setorigin(g,self.origin); + g.angles = self.angles; + +#ifdef FTE + + g.alpha = 0.5; + +#endif // FTE + + } +} + +void() allocate_floating_weapons = +{ + self.think = SUB_Null; + + // Spawn all of our floating weapon entities + // Multiply by 2 to account for the glow. + for (float i = 0; i < mystery_box_count * 3; i++) { + entity tempe = spawn(); + tempe.classname = "freeMboxEntity"; + } + + finish_mbox_setup(); +} + +void() Create_Floating_Weapon = +{ + entity gun; + float r, tempf; + string temps; + + tempf = randomweapon(); + r = Getweaponid(tempf); + while (!CheckWeapon (r, self.owner.owner)) + { + tempf = randomweapon(); + r = Getweaponid(tempf); + } + temps = GetWeaponModel(r, 1); + + gun = MBOX_GetFreeEnt(); + gun.classname = "mystery_weapon"; + + setorigin (gun, self.origin); + setmodel (gun, temps); + setsize (gun, '0 0 0', '0 0 0'); + gun.angles = self.angles; + gun.effects = EF_FULLBRIGHT; + + gun.movetype = MOVETYPE_NOCLIP; + gun.solid = SOLID_NOT; + makevectors(self.angles); + gun.velocity = v_up*15; + + gun.owner = self; + self.boxweapon = gun; + + gun.ltime = time+2; + gun.boxstatus = 0.01; + gun.wait = time + 5; + gun.nextthink = time + 0.01; + gun.think = Float_Change; +} + +void() mystery_box_tp_spot = +{ + precache_model ("models/props/teddy.mdl"); + precache_sound ("sounds/pu/byebye.wav"); + precache_sound ("sounds/misc/giggle.wav"); + + self.solid=SOLID_TRIGGER; + self.classname = "mystery_box_tp_spot"; + setorigin(self, self.origin); + setmodel (self, "models/props/teddy.mdl"); + setsize (self, VEC_HULL2_MIN, VEC_HULL2_MAX); + + if (self.model == "models/props/teddy.mdl") { + self.frame = 2; + self.angles_y -= 90; + } + + mystery_boxes[mystery_box_count] = self; + mystery_box_count++; +}; + +void() mystery_touch = +{ + entity tempe; + + if (other.classname != "player" || other.downed) + return; + + if (!self.boxstatus) { + useprint (other, 6, 950, 0); + } + if (self.boxstatus == 2 && self.owner == other) { + useprint (other, 7, 0, 0); + } + + if (other.button7 && !other.semiuse) + { + other.semiuse = true; + if (!self.boxstatus) + { + if (other.points >= 950) + { + sound (self, CHAN_ITEM, "sounds/machines/mbox_open.wav", 1, ATTN_NORM); + addmoney(other, -950, FALSE); + self.boxstatus = 1; + self.owner = other; + box_open1 (); + Create_Floating_Weapon(); + self.spins++; + } + else { + centerprint (other, STR_NOTENOUGHPOINTS); + sound(other, 0, "sounds/misc/denybuy.wav", 1, 1); + } + } + if (self.boxstatus == 2) + { + if (self.owner == other) + { + other.reload_delay = 0; + self.boxstatus = 0; + self.owner = world; + if (other.weapon != self.boxweapon.weapon && other.secondaryweapon != self.boxweapon.weapon && other.secondaryweapon && other.thirdweapon != self.boxweapon.weapon) + { + if ((other.perks & P_MULE) && !other.thirdweapon) { + // store secondary weapon + local float tempf = other.secondaryweapon; + local float tempf1 = other.secondarymag; + local float tempf2 = other.secondaryammo; + local float tempf3 = other.secondarymag2; + // move primary to secondary + other.secondaryweapon = other.weapon; + other.secondarymag = other.currentmag; + other.secondarymag2 = other.currentmag2; + other.secondaryammo = other.currentammo; + // move secondary to tertiary + other.thirdweapon = tempf; + other.thirdmag = tempf1; + other.thirdammo = tempf2; + other.thirdmag2 = tempf3; + } + + // give boxweapon + other.weapon = self.boxweapon.weapon; + other.currentammo = getWeaponAmmo(self.boxweapon.weapon); + other.currentmag = getWeaponMag(self.boxweapon.weapon); + other.weaponskin = 0; + + if (other.weapon != W_KAR_SCOPE && other.weapon != W_HEADCRACKER && !IsDualWeapon(other.weapon)) { + other.weapon2model = ""; + } + +#ifndef FTE + + other.Flash_Offset = GetWeaponFlash_Offset(self.boxweapon.weapon); + other.Flash_Size = GetWeaponFlash_Size(self.boxweapon.weapon); + other.Weapon_Name = GetWeaponName(self.boxweapon.weapon); + +#endif // FTE + + } + else if (other.weapon == self.boxweapon.weapon) + { + other.currentammo = getWeaponAmmo(self.boxweapon.weapon); + other.currentmag = getWeaponMag(self.boxweapon.weapon); + } + else if (other.secondaryweapon == self.boxweapon.weapon) + { + other.secondaryammo = getWeaponAmmo(self.boxweapon.weapon); + other.secondarymag = getWeaponMag(self.boxweapon.weapon); + } + else if (other.thirdweapon == self.boxweapon.weapon) + { + other.thirdammo = getWeaponAmmo(self.boxweapon.weapon); + other.thirdmag = getWeaponMag(self.boxweapon.weapon); + } + else if (!other.secondaryweapon) + { + WeaponSwitch(other); + other.weapon = self.boxweapon.weapon; + other.currentammo = getWeaponAmmo(self.boxweapon.weapon); + other.currentmag = getWeaponMag(self.boxweapon.weapon); + other.weaponskin = GetWepSkin(self.boxweapon.weapon); + +#ifndef FTE + + other.Flash_Offset = GetWeaponFlash_Offset(self.boxweapon.weapon); + other.Flash_Size = GetWeaponFlash_Size(self.boxweapon.weapon); + other.Weapon_Name = GetWeaponName(self.boxweapon.weapon); + +#endif // FTE + + } + else if (!other.thirdweapon && (other.perks & P_MULE)) + { + + } + sound(self, 0,"sounds/misc/ching.wav", 1, 1); + tempe = self; + self = other; + + if (GetFrame(self.weapon, FIRST_TAKE_START) != 0) + Weapon_PlayViewModelAnimation(ANIM_FIRST_TAKE, SUB_Null, 0); + else + Weapon_PlayViewModelAnimation(ANIM_TAKE_OUT, SUB_Null, 0); + + SwitchWeapon(self.weapon); + self = tempe; + MBOX_FreeEnt(self.boxweapon); + box_close1(); + } + } + } +} + + +// +// Load_Mbox_Data() +// Opens the map's MBOX Data File and adds parsed data +// into the Box's weapon Array. +// ---------- +// TODO: Possibly investigate making this a little better and more modular +// so adding MBOX Weapons can be easier? +// +void() Load_Mbox_Data = +{ + float file; + string h; + int weapons_all_disabled = 1; + + // Attempt to Open the File + h = strcat(mapname, ".mbox"); + h = strcat("maps/", h); + file = fopen (h, FILE_READ); + + // There was no MBOX Data, enable all Weapons! + // R.I.P. PSP Memory 90% of the time in this case.. + if (file == -1) { + for (float i = 0; i < 27; i++) { + BoxWeapons[i] = 1; + } + fclose(file); + } else { + // Parse each Line and write the Data into our Array. + for (float i = 0; i < 27; i++) { + h = strtrim((fgets(file))); + BoxWeapons[i] = stof(h); + + if (stof(h) == 1) + weapons_all_disabled = 0; + } + } + + if (weapons_all_disabled) { + for (float i = 0; i < 27; i++) { + BoxWeapons[i] = 1; + } + } + + for(float i = 0; i < 27; i++) { + // Precache Weapon Data if enabled + if (BoxWeapons[i]) { + precache_model(GetWeaponModel(Getweaponid(i), 0)); + precache_model(GetWeaponModel(Getweaponid(i), 1)); + precache_extra(Getweaponid(i)); + } + } + + fclose(file); +} + +void() mystery_box = +{ + Load_Mbox_Data(); + + precache_model ("models/machines/mystery.mdl"); + precache_sound ("sounds/machines/mbox_open.wav"); + precache_sound ("sounds/machines/mbox_close.wav"); + + if (!(self.spawnflags & MBOX_SPAWNFLAG_NOLIGHT)) + precache_model ("models/machines/mglow$.mdl"); + + self.solid = SOLID_TRIGGER; + self.classname = "mystery"; + setorigin(self, self.origin); + setmodel (self, "models/machines/mystery.mdl"); + setsize (self, VEC_HULL2_MIN, VEC_HULL2_MAX); + + self.touch = mystery_touch; + boxOrigin = self.origin; + + mystery_boxes[mystery_box_count] = self; + mystery_box_count++; + + self.think = allocate_floating_weapons; + self.nextthink = time + 0.2; +} \ No newline at end of file