openmohaa/code/fgame/item.cpp

1009 lines
21 KiB
C++

/*
===========================================================================
Copyright (C) 2015 the OpenMoHAA team
This file is part of OpenMoHAA source code.
OpenMoHAA source code 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.
OpenMoHAA source code 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 OpenMoHAA source code; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
===========================================================================
*/
// item.cpp: Base class for respawnable, carryable objects.
//
#include "g_local.h"
#include "g_phys.h"
#include "entity.h"
#include "trigger.h"
#include "item.h"
#include "inventoryitem.h"
#include "scriptmaster.h"
#include "health.h"
#include "game.h"
typedef struct {
str name;
str prefix;
bool mohprefix;
} prefix_t;
static prefix_t r_prefixlist[256] = {
{"Unarmed", "unarmed", true},
{"Binoculars", "binoculars", true},
{"Papers", "papers", true},
{"Packed MG42 Turret", "mg42portable", true},
{"Colt 45", "colt45", true},
{"Walther P38", "p38", true},
{"Hi-Standard Silenced", "histandard", true},
{"Webley Revolver", "webley", true},
{"Nagant Revolver", "nagantrev", true},
{"Beretta", "beretta", true},
{"M1 Garand", "garand", true},
{"Mauser KAR 98K", "kar98", true},
{"KAR98 - Sniper", "kar98sniper", true},
{"Springfield '03 Sniper", "springfield", true},
{"Lee-Enfield", "enfield", true},
{"SVT 40", "svt", true},
{"Mosin Nagant Rifle", "mosin", true},
{"G 43", "g43", true},
{"Enfield L42A1", "enfieldl42a1", true},
{"Carcano", "carcano", true},
{"DeLisle", "delisle", true},
{"Thompson", "thompson", true},
{"MP40", "mp40", true},
{"Sten Mark II", "sten", true},
{"PPSH SMG", "ppsh", true},
{"Moschetto", "moschetto", true},
{"BAR", "bar", true},
{"StG 44", "mp44", true},
{"FG 42", "fg42", true},
{"Vickers-Berthier", "vickers", true},
{"Breda", "breda", true},
{"Frag Grenade", "fraggrenade", true},
{"Stielhandgranate", "stielhandgranate", true},
{"F1 Grenade", "f1grenade", true},
{"Mills Grenade", "millsgrenade", true},
{"Nebelhandgranate", "nebelhandgranate", true},
{"M18 Smoke Grenade", "m18smokegrenade", true},
{"RDG-1 Smoke Grenade", "rdg1smokegrenade", true},
{"Bomba A Mano", "bomba", true},
{"Bomba A Mano Breda", "bombabreda", true},
{"Landmine", "mine", true},
{"LandmineAllies", "minedetector", true},
{"LandmineAxis", "minedetectoraxis", true},
{"Unarmed", "detonator", true},
{"Bazooka", "bazooka", true},
{"Panzerschreck", "panzerschreck", true},
{"Gewehrgranate", "kar98mortar", true},
{"Shotgun", "shotgun", true},
{"PIAT", "PIAT", true},
};
void AddItemToList(const char *name, const char *prefix)
{
for (int i = 0; i < sizeof(r_prefixlist) / sizeof(r_prefixlist[0]); i++) {
if (!r_prefixlist[i].name.length()) {
r_prefixlist[i].name = name;
r_prefixlist[i].prefix = prefix;
r_prefixlist[i].mohprefix = false;
return;
}
}
}
const char *GetItemName(const char *prefix, qboolean *mohprefix)
{
for (int i = 0; i < sizeof(r_prefixlist) / sizeof(r_prefixlist[0]); i++) {
if (!r_prefixlist[i].prefix.c_str()) {
continue;
}
if (r_prefixlist[i].prefix == prefix) {
if (mohprefix) {
*mohprefix = r_prefixlist[i].mohprefix;
}
return r_prefixlist[i].name;
}
}
if (mohprefix) {
*mohprefix = false;
}
return "Unarmed";
}
const char *GetItemPrefix(const char *name, qboolean *mohprefix)
{
for (int i = 0; i < sizeof(r_prefixlist) / sizeof(r_prefixlist[0]); i++) {
if (r_prefixlist[i].name == name) {
if (mohprefix) {
*mohprefix = r_prefixlist[i].mohprefix;
}
return r_prefixlist[i].prefix;
}
}
if (mohprefix) {
*mohprefix = false;
}
return "unarmed";
}
Event EV_Item_Pickup
(
"item_pickup",
EV_DEFAULT,
"e",
"item",
"Pickup the specified item.",
EV_NORMAL
);
Event EV_Item_DropToFloor
(
"item_droptofloor",
EV_DEFAULT,
NULL,
NULL,
"Drops the item to the ground.",
EV_NORMAL
);
Event EV_Item_Respawn
(
"respawn",
EV_DEFAULT,
NULL,
NULL,
"Respawns the item.",
EV_NORMAL
);
Event EV_Item_SetRespawn
(
"set_respawn",
EV_DEFAULT,
"i",
"respawn",
"Turns respawn on or off.",
EV_NORMAL
);
Event EV_Item_SetRespawnTime
(
"set_respawn_time",
EV_DEFAULT,
"f",
"respawn_time",
"Sets the respawn time.",
EV_NORMAL
);
Event EV_Item_SetAmount
(
"amount",
EV_DEFAULT,
"i",
"amount",
"Sets the amount of the item.",
EV_NORMAL
);
Event EV_Item_SetMaxAmount
(
"maxamount",
EV_DEFAULT,
"i",
"max_amount",
"Sets the max amount of the item.",
EV_NORMAL
);
Event EV_Item_SetDMAmount
(
"dmamount",
EV_DEFAULT,
"i",
"amount",
"Sets the amount of the item for DM.",
EV_NORMAL
);
Event EV_Item_SetDMMaxAmount
(
"dmmaxamount",
EV_DEFAULT,
"i",
"max_amount",
"Sets the max amount of the item fmr DM.",
EV_NORMAL
);
Event EV_Item_SetItemName
(
"name",
EV_DEFAULT,
"s",
"item_name",
"Sets the item name.",
EV_NORMAL
);
Event EV_Item_RespawnSound
(
"respawnsound",
EV_DEFAULT,
NULL,
NULL,
"Turns on the respawn sound for this item.",
EV_NORMAL
);
Event EV_Item_DialogNeeded
(
"dialogneeded",
EV_DEFAULT,
"s",
"dialog_needed",
"Sets the dialog needed string.",
EV_NORMAL
);
Event EV_Item_NoRemove
(
"no_remove",
EV_DEFAULT,
NULL,
NULL,
"Makes it so the item is not removed from the world when it is picked up.",
EV_NORMAL
);
Event EV_Item_RespawnDone
(
"respawn_done",
EV_DEFAULT,
NULL,
NULL,
"Called when the item respawn is done.",
EV_NORMAL
);
Event EV_Item_PickupDone
(
"pickup_done",
EV_DEFAULT,
NULL,
NULL,
"Called when the item pickup is done.",
EV_NORMAL
);
Event EV_Item_SetPickupSound
(
"pickupsound",
EV_DEFAULT,
"s",
"name",
"sets the item's pickup sound alias",
EV_NORMAL
);
Event EV_Item_ViewModelPrefix
(
"viewmodelprefix",
EV_DEFAULT,
"s",
"prefix",
"Sets the item's prefix for viewmodelanim.",
EV_NORMAL
);
Event EV_Item_UpdatePrefix
(
"_updateprefix",
EV_CODEONLY,
NULL,
NULL,
"internal event - update the custom viewmodel prefix",
EV_NORMAL
);
CLASS_DECLARATION(Trigger, Item, NULL) {
{&EV_Trigger_Effect, &Item::ItemTouch },
{&EV_Item_DropToFloor, &Item::DropToFloor },
{&EV_Item_Respawn, &Item::Respawn },
{&EV_Item_SetAmount, &Item::SetAmountEvent },
{&EV_Item_SetMaxAmount, &Item::SetMaxAmount },
{&EV_Item_SetItemName, &Item::SetItemName },
{&EV_Item_Pickup, &Item::Pickup },
{&EV_Use, &Item::TriggerStuff },
{&EV_Item_RespawnSound, &Item::RespawnSound },
{&EV_Item_DialogNeeded, &Item::DialogNeeded },
{&EV_Item_NoRemove, &Item::SetNoRemove },
{&EV_Item_RespawnDone, &Item::RespawnDone },
{&EV_Item_PickupDone, &Item::PickupDone },
{&EV_Item_SetRespawn, &Item::setRespawn },
{&EV_Item_SetRespawnTime, &Item::setRespawnTime },
{&EV_Stop, &Item::Landed },
{&EV_SetAngle, &SimpleEntity::SetAngleEvent},
{&EV_Item_SetDMAmount, &Item::SetDMAmountEvent },
{&EV_Item_SetDMMaxAmount, &Item::SetDMMaxAmount },
{&EV_Item_SetPickupSound, &Item::SetPickupSound },
{&EV_Item_ViewModelPrefix, &Item::EventViewModelPrefix },
{&EV_Item_UpdatePrefix, &Item::updatePrefix },
{NULL, NULL }
};
Item::Item()
{
str fullname;
entflags |= ECF_ITEM;
AddWaitTill(STRING_PICKUP);
if (LoadingSavegame) {
return;
}
setSolidType(SOLID_NOT);
// Set default respawn behavior
// Derived classes should use setRespawn
// if they want to override the default behavior
setRespawn(deathmatch->integer ? true : false);
setRespawnTime(20);
//
// set a minimum mins and maxs for the model
//
if (size.length() < 10) {
mins = "-10 -10 0";
maxs = "10 10 20";
}
//
// reset the mins and maxs to pickup the FL_ROTATEDBOUNDS flag
//
setSize(mins, maxs);
// Items can't be immediately dropped to floor, because they might
// be on an entity that hasn't spawned yet.
PostEvent(EV_Item_DropToFloor, EV_POSTSPAWN);
respondto = TRIGGER_PLAYERS;
// items should collide with everything that the player does
edict->clipmask = MASK_ITEM;
item_index = 0;
maximum_amount = 1;
playrespawn = false;
// this is an item entity
edict->s.eType = ET_ITEM;
amount = 1;
no_remove = false;
setName("Unknown Item");
sPickupSound = "snd_pickup";
m_sVMprefix = "Unarmed";
m_bMOHPrefix = true;
}
Item::~Item()
{
if (owner) {
owner->RemoveItem(this);
owner = NULL;
}
entflags &= ~ECF_ITEM;
}
void Item::RemoveFromOwner(void)
{
owner->RemoveItem(this);
owner = NULL;
}
void Item::Delete(void)
{
if (g_iInThinks) {
if (owner) {
RemoveFromOwner();
}
PostEvent(EV_Remove, 0);
} else {
delete this;
}
}
void Item::SetNoRemove(Event *ev)
{
no_remove = true;
}
/*
============
PlaceItem
Puts an item back in the world
============
*/
void Item::PlaceItem(void)
{
setSolidType(SOLID_TRIGGER);
setMoveType(MOVETYPE_TOSS);
showModel();
groundentity = NULL;
setSize(Vector(-12, -12, -2), Vector(12, 12, 12));
}
/*
============
DropToFloor
plants the object on the floor
============
*/
void Item::DropToFloor(Event *ev)
{
str fullname;
Vector save;
PlaceItem();
setMoveType(MOVETYPE_NONE);
/*
addOrigin( Vector( "0 0 1" ) );
save = origin;
if( !droptofloor( 8192 ) )
{
gi.DPrintf( "%s (%d) stuck in world at '%5.1f %5.1f %5.1f'\n",
getClassID(), entnum, origin.x, origin.y, origin.z );
setOrigin( save );
setMoveType( MOVETYPE_NONE );
}
else
{
setMoveType( MOVETYPE_NONE );
}
//
// if the our global variable doesn't exist, lets zero it out
//
fullname = str( "playeritem_" ) + getName();
game.vars->GetOrCreateVariable( fullname.c_str() );
level.vars->GetOrCreateVariable( fullname.c_str() );
*/
}
qboolean Item::Drop(void)
{
if (!owner) {
return false;
}
setOrigin(owner->origin + Vector("0 0 40"));
// drop the item
PlaceItem();
velocity = owner->velocity * 0.5 + Vector(G_CRandom(50), G_CRandom(50), 100);
setAngles(owner->angles);
avelocity = Vector(0, G_CRandom(360), 0);
trigger_time = level.time + 1;
if (owner->isClient()) {
spawnflags |= DROPPED_PLAYER_ITEM;
} else {
spawnflags |= DROPPED_ITEM;
}
// Remove this from the owner's item list
RemoveFromOwner();
PostEvent(EV_Remove, g_droppeditemlife->value);
return true;
}
void Item::ItemTouch(Event *ev)
{
Entity *other;
Event *e;
if (owner) {
// Don't respond to trigger events after item is picked up.
// we really don't need to see this.
//gi.DPrintf( "%s with targetname of %s was triggered unexpectedly.\n", getClassID(), TargetName() );
return;
}
other = ev->GetEntity(1);
e = new Event(EV_Item_Pickup);
e->AddEntity(other);
ProcessEvent(e);
}
void Item::SetOwner(Sentient *ent)
{
assert(ent);
if (!ent) {
// return to avoid any buggy behaviour
return;
}
owner = ent;
setRespawn(false);
setSolidType(SOLID_NOT);
hideModel();
CancelEventsOfType(EV_Touch);
CancelEventsOfType(EV_Item_DropToFloor);
CancelEventsOfType(EV_Remove);
Event *ev = new Event(EV_Item_UpdatePrefix);
ev->AddEntity(ent);
PostEvent(ev, EV_POSTSPAWN);
}
Sentient *Item::GetOwner(void)
{
return owner;
}
Item *Item::ItemPickup(Entity *other, qboolean add_to_inventory)
{
Sentient *sent;
Item *item = NULL;
str realname;
if (!Pickupable(other)) {
return NULL;
}
sent = (Sentient *)other;
if (add_to_inventory) {
item = sent->giveItem(model, getAmount());
if (!item) {
return NULL;
}
} else {
item = this;
}
//
// let our sent know they received it
// we put this here so we can transfer information from the original item we picked up
//
sent->ReceivedItem(item);
Sound(sPickupSound);
if (!Removable()) {
// leave the item for others to pickup
return item;
}
CancelEventsOfType(EV_Item_DropToFloor);
CancelEventsOfType(EV_Item_Respawn);
CancelEventsOfType(EV_FadeOut);
setSolidType(SOLID_NOT);
if (HasAnim("pickup")) {
NewAnim("pickup", EV_Item_PickupDone);
} else {
if (!no_remove) {
hideModel();
if (!Respawnable()) {
PostEvent(EV_Remove, FRAMETIME);
}
}
}
if (Respawnable()) {
PostEvent(EV_Item_Respawn, RespawnTime());
}
return item;
}
void Item::Respawn(Event *ev)
{
showModel();
// allow it to be touched again
setSolidType(SOLID_TRIGGER);
// play respawn sound
if (playrespawn) {
Sound("snd_itemspawn");
}
setOrigin();
if (HasAnim("respawn")) {
NewAnim("respawn", EV_Item_RespawnDone);
}
}
void Item::setRespawn(Event *ev)
{
if (ev->NumArgs() < 1) {
return;
}
setRespawn(ev->GetInteger(1));
}
void Item::setRespawnTime(Event *ev)
{
if (ev->NumArgs() < 1) {
return;
}
setRespawnTime(ev->GetFloat(1));
}
void Item::RespawnDone(Event *ev)
{
NewAnim("idle");
}
void Item::PickupDone(Event *ev)
{
if (!no_remove) {
hideModel();
if (!Respawnable()) {
PostEvent(EV_Remove, FRAMETIME);
}
} else {
if (HasAnim("pickup_idle")) {
NewAnim("pickup_idle");
} else {
NewAnim("pickup");
}
}
}
void Item::setRespawn(qboolean flag)
{
respawnable = flag;
}
qboolean Item::Respawnable(void)
{
return respawnable;
}
void Item::setRespawnTime(float time)
{
respawntime = time;
}
float Item::RespawnTime(void)
{
return respawntime;
}
int Item::getAmount(void)
{
return amount;
}
int Item::MaxAmount(void)
{
return maximum_amount;
}
qboolean Item::Pickupable(Entity *other)
{
if (!other->IsSubclassOfSentient()) {
return false;
} else {
Sentient *sent;
Item *item;
sent = (Sentient *)other;
item = sent->FindItem(getName());
if (item && (item->getAmount() >= item->MaxAmount())) {
return false;
}
}
return true;
}
void Item::Pickup(Event *ev)
{
ItemPickup(ev->GetEntity(1));
}
void Item::setName(const char *i)
{
const char *prefix;
item_name = i;
item_index = gi.itemindex(i);
strcpy(edict->entname, i);
prefix = GetItemPrefix(item_name);
if (prefix) {
m_sVMprefix = prefix;
m_bMOHPrefix = true;
}
}
str Item::getName(void)
{
return (item_name);
}
int Item::getIndex(void)
{
return item_index;
}
void Item::setAmount(int startamount)
{
amount = startamount;
if (amount >= MaxAmount()) {
SetMax(amount);
}
}
void Item::SetMax(int maxamount)
{
maximum_amount = maxamount;
}
void Item::SetAmountEvent(Event *ev)
{
if (g_protocol <= protocol_e::PROTOCOL_MOH && g_gametype->integer != GT_SINGLE_PLAYER) {
return;
}
setAmount(ev->GetInteger(1));
}
void Item::SetMaxAmount(Event *ev)
{
if (g_protocol <= protocol_e::PROTOCOL_MOH && g_gametype->integer != GT_SINGLE_PLAYER) {
return;
}
SetMax(ev->GetInteger(1));
}
void Item::SetDMAmountEvent(Event *ev)
{
if (!g_gametype->integer) {
return;
}
setAmount(ev->GetInteger(1));
}
void Item::SetDMMaxAmount(Event *ev)
{
if (!g_gametype->integer) {
return;
}
setAmount(ev->GetInteger(1));
}
void Item::SetPickupSound(Event *ev)
{
sPickupSound = ev->GetString(1);
}
void Item::SetItemName(Event *ev)
{
setName(ev->GetString(1));
}
void Item::Add(int num)
{
amount += num;
if (amount >= MaxAmount()) {
amount = MaxAmount();
}
}
void Item::Remove(int num)
{
amount -= num;
if (amount < 0) {
amount = 0;
}
}
qboolean Item::Use(int num)
{
if (num > amount) {
return false;
}
amount -= num;
return true;
}
qboolean Item::Removable(void)
{
return true;
}
void Item::RespawnSound(Event *ev)
{
playrespawn = true;
}
void Item::DialogNeeded(Event *ev)
{
//
// if this item is needed for a trigger, play this dialog
//
dialog_needed = ev->GetString(1);
}
str Item::GetDialogNeeded(void)
{
return dialog_needed;
}
//
// once item has landed on the floor, go to movetype none
//
void Item::Landed(Event *ev)
{
if (groundentity && (groundentity->entity != world)) {
warning("Item::Landed", "Item %d has landed on an entity that might move\n", entnum);
}
setMoveType(MOVETYPE_NONE);
}
void Item::EventViewModelPrefix(Event *ev)
{
int i;
gentity_t *ent;
m_sVMprefix = ev->GetString(1);
if (!GetItemPrefix(item_name, &m_bMOHPrefix)) {
AddItemToList(item_name, m_sVMprefix);
}
for (i = 0, ent = g_entities; i < game.maxclients; i++, ent++) {
if (!ent->inuse || !ent->entity) {
continue;
}
Event *ev = new Event(EV_Item_UpdatePrefix);
ev->AddEntity(ent->entity);
PostEvent(ev, EV_POSTSPAWN);
}
}
void Item::updatePrefix(Event *ev)
{
if (!level.specialgame) {
return;
}
if (m_bMOHPrefix) {
return;
}
// FIXME: delete
/*
Entity *ent = ev->GetEntity( 1 );
gi.MSG_SetClient( ent->edict - g_entities );
gi.MSG_StartCGM( CGM_VIEWMODELPREFIX );
gi.MSG_WriteString( item_name.c_str() );
gi.MSG_WriteString( m_sVMprefix.c_str() );
gi.MSG_EndCGM();
*/
}
CLASS_DECLARATION(Item, DynItem, NULL) {
{&EV_Kill, &DynItem::UnlinkItem},
{&EV_Damage, &DynItem::UnlinkItem},
{NULL, NULL }
};
DynItem::DynItem()
{
if (LoadingSavegame) {
return;
}
setSolidType(SOLID_BBOX);
setMoveType(MOVETYPE_BOUNCE);
takedamage = DAMAGE_YES;
}
void DynItem::UnlinkItem(Event *ev)
{
if (!owner) {
return;
}
setOrigin(owner->origin + Vector(0, 0, 40));
PlaceItem();
velocity = owner->velocity * 0.5 + Vector(G_CRandom(50), G_CRandom(50), 100);
setAngles(owner->angles);
avelocity = Vector(0, G_CRandom(360), 0);
trigger_time = level.time + 1;
if (owner->isClient()) {
spawnflags |= DROPPED_PLAYER_ITEM;
} else {
spawnflags |= DROPPED_ITEM;
}
// Remove this from the owner's item list
RemoveFromOwner();
}
void DynItem::DynItemTouched(Event *ev)
{
// Empty
}
void DynItem::DynItemUse(Event *ev)
{
// Empty
}
void DynItem::Archive(Archiver& arc)
{
Item::Archive(arc);
arc.ArchiveString(&m_attachPrime);
arc.ArchiveString(&m_attachSec);
arc.ArchiveString(&m_dynItemName);
if (arc.Loading()) {
setName(m_dynItemName.c_str());
}
}