gzdoom/src/g_inventory/a_keys.cpp
Christoph Oelckers ddab2c3e78 - removed the playernum parameter from CheckLocalView
This was always used with 'consoleplayer' which really is the only thing making sense here. But this is a part of the global state which should be avoided in play code.

In particular, this makes no real sense in case of secondary maps where it should always return false.
2019-01-31 03:29:25 +01:00

571 lines
13 KiB
C++

/*
** a_keys.cpp
** Implements all keys and associated data
**
**---------------------------------------------------------------------------
** Copyright 2005-2016 Cheistoph Oelckers
** All rights reserved.
**
** Redistribution and use in source and binary forms, with or without
** modification, are permitted provided that the following conditions
** are met:
**
** 1. Redistributions of source code must retain the above copyright
** notice, this list of conditions and the following disclaimer.
** 2. Redistributions in binary form must reproduce the above copyright
** notice, this list of conditions and the following disclaimer in the
** documentation and/or other materials provided with the distribution.
** 3. The name of the author may not be used to endorse or promote products
** derived from this software without specific prior written permission.
**
** THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
** IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
** OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
** IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
** INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
** NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
** THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
**---------------------------------------------------------------------------
**
*/
#include "a_keys.h"
#include "tarray.h"
#include "gi.h"
#include "gstrings.h"
#include "d_player.h"
#include "c_console.h"
#include "w_wad.h"
#include "v_font.h"
#include "vm.h"
//===========================================================================
//
// Data for the LOCKDEFS
//
//===========================================================================
//===========================================================================
//
//
//===========================================================================
struct OneKey
{
PClassActor *key;
int count;
bool check(AActor *owner)
{
// P_GetMapColorForKey() checks the key directly
if (owner->IsA(key) || owner->GetSpecies() == key->TypeName) return true;
// Other calls check an actor that may have a key in its inventory.
for (AActor *item = owner->Inventory; item != NULL; item = item->Inventory)
{
if (item->IsA(key))
{
return true;
}
else if (item->GetSpecies() == key->TypeName)
{
return true;
}
}
return false;
}
};
//===========================================================================
//
//
//===========================================================================
struct Keygroup
{
TArray<OneKey> anykeylist;
bool check(AActor *owner)
{
for(unsigned int i=0;i<anykeylist.Size();i++)
{
if (anykeylist[i].check(owner)) return true;
}
return false;
}
};
//===========================================================================
//
//
//===========================================================================
struct Lock
{
TArray<Keygroup> keylist;
TArray<FSoundID> locksound;
FString Message;
FString RemoteMsg;
int rgb = 0;
bool check(AActor * owner)
{
// An empty key list means that any key will do
if (!keylist.Size())
{
auto kt = PClass::FindActor(NAME_Key);
for (AActor *item = owner->Inventory; item != NULL; item = item->Inventory)
{
if (item->IsKindOf (kt))
{
return true;
}
}
return false;
}
else for(unsigned int i=0;i<keylist.Size();i++)
{
if (!keylist[i].check(owner)) return false;
}
return true;
}
};
static TMap<int, Lock> Locks;
static bool keysdone = false; // have the locks been initialized?
static TArray<PClassActor *> KeyTypes; // List of all keys sorted by lock. This is for use by the status bar to draw ordered key lists.
//===========================================================================
//
//
//===========================================================================
static const char * keywords_lock[]={
"ANY",
"MESSAGE",
"REMOTEMESSAGE",
"MAPCOLOR",
"LOCKEDSOUND",
NULL
};
//===========================================================================
//
//
//===========================================================================
static void AddOneKey(Keygroup *keygroup, PClassActor *mi, FScanner &sc, bool ignorekey, int &currentnumber)
{
if (mi)
{
// Any inventory item can be used to unlock a door
if (mi->IsDescendantOf(NAME_Inventory))
{
OneKey k = {mi,1};
keygroup->anykeylist.Push (k);
//... but only keys get key numbers!
if (mi->IsDescendantOf(NAME_Key))
{
if (!ignorekey &&
GetDefaultByType(mi)->special1 == 0)
{
GetDefaultByType(mi)->special1 = ++currentnumber;
}
}
}
else
{
sc.ScriptError("'%s' is not an inventory item", sc.String);
}
}
else
{
sc.ScriptError("Unknown item '%s'", sc.String);
}
}
//===========================================================================
//
//
//===========================================================================
static void ParseKeygroup(Keygroup *keygroup, FScanner &sc, bool ignorekey, int &currentnumber)
{
PClassActor *mi;
sc.MustGetStringName("{");
while (!sc.CheckString("}"))
{
sc.MustGetString();
mi = PClass::FindActor(sc.String);
AddOneKey(keygroup, mi, sc, ignorekey, currentnumber);
}
keygroup->anykeylist.ShrinkToFit();
}
//===========================================================================
//
//
//===========================================================================
static void PrintMessage (const char *str)
{
if (str != NULL)
{
if (str[0]=='$')
{
str = GStrings(str+1);
}
C_MidPrint (SmallFont, str);
}
}
//===========================================================================
//
//
//===========================================================================
static void ParseLock(FScanner &sc, int &currentnumber)
{
int i,r,g,b;
int keynum;
Lock sink;
PClassActor *mi;
sc.MustGetNumber();
keynum = sc.Number;
sc.MustGetString();
if (!sc.Compare("{"))
{
if (!CheckGame(sc.String, false)) keynum = -1;
sc.MustGetStringName("{");
}
if (keynum == 0 || keynum < -1)
{
sc.ScriptError("Lock index %d out of range", keynum);
}
bool ignorekey = keynum == -1; // tell the parsing functions to ignore what they read for other games' keys.
auto lock = keynum == -1? &sink : &Locks.InsertNew(keynum);
lock->locksound.Push("*keytry");
lock->locksound.Push("misc/keytry");
while (!sc.CheckString("}"))
{
sc.MustGetString();
switch(i = sc.MatchString(keywords_lock))
{
case 0: // Any
{
lock->keylist.Reserve(1);
ParseKeygroup(&lock->keylist.Last(), sc, ignorekey, currentnumber);
if (lock->keylist.Last().anykeylist.Size() == 0)
{
lock->keylist.Pop();
}
break;
}
case 1: // message
sc.MustGetString();
lock->Message = sc.String;
break;
case 2: // remotemsg
sc.MustGetString();
lock->RemoteMsg = sc.String;
break;
case 3: // mapcolor
sc.MustGetNumber();
r = sc.Number;
sc.MustGetNumber();
g = sc.Number;
sc.MustGetNumber();
b = sc.Number;
lock->rgb = MAKERGB(r,g,b);
break;
case 4: // locksound
lock->locksound.Clear();
for (;;)
{
sc.MustGetString();
lock->locksound.Push(sc.String);
if (!sc.GetString())
{
break;
}
if (!sc.Compare(","))
{
sc.UnGet();
break;
}
}
break;
default:
mi = PClass::FindActor(sc.String);
if (mi)
{
lock->keylist.Reserve(1);
AddOneKey(&lock->keylist.Last(), mi, sc, ignorekey, currentnumber);
if (lock->keylist.Last().anykeylist.Size() == 0)
{
lock->keylist.Pop();
}
}
break;
}
}
// copy the messages if the other one does not exist
if (lock->RemoteMsg.IsEmpty() && lock->Message.IsNotEmpty())
{
lock->RemoteMsg = lock->Message;
}
if (lock->Message.IsEmpty() && lock->RemoteMsg.IsNotEmpty())
{
lock->Message = lock->RemoteMsg;
}
lock->keylist.ShrinkToFit();
}
//===========================================================================
//
// Clears all key numbers so the parser can assign its own ones
// This ensures that only valid keys are considered by the key cheats
//
//===========================================================================
static void ClearLocks()
{
unsigned int i;
auto kt = PClass::FindActor(NAME_Key);
for(i = 0; i < PClassActor::AllActorClasses.Size(); i++)
{
if (PClassActor::AllActorClasses[i]->IsDescendantOf(kt))
{
auto key = GetDefaultByType(PClassActor::AllActorClasses[i]);
if (key != NULL)
{
key->special1 = 0;
}
}
}
Locks.Clear();
keysdone = false;
}
//---------------------------------------------------------------------------
//
// create a sorted list of the defined keys so
// this doesn't have to be done each frame
//
// For use by the HUD and statusbar code to get a consistent order.
//
//---------------------------------------------------------------------------
static int ktcmp(const void * a, const void * b)
{
auto key1 = GetDefaultByType(*(PClassActor **)a);
auto key2 = GetDefaultByType(*(PClassActor **)b);
return key1->special1 - key2->special1;
}
static void CreateSortedKeyList()
{
TArray<PClassActor *> UnassignedKeyTypes;
KeyTypes.Clear();
for (unsigned int i = 0; i < PClassActor::AllActorClasses.Size(); i++)
{
PClassActor *ti = PClassActor::AllActorClasses[i];
auto kt = PClass::FindActor(NAME_Key);
if (ti->IsDescendantOf(kt))
{
auto key = GetDefaultByType(ti);
if (key->special1 > 0)
{
KeyTypes.Push(ti);
}
else
{
UnassignedKeyTypes.Push(ti);
}
}
}
if (KeyTypes.Size())
{
qsort(&KeyTypes[0], KeyTypes.Size(), sizeof(KeyTypes[0]), ktcmp);
}
KeyTypes.Append(UnassignedKeyTypes);
}
//===========================================================================
//
// P_InitKeyMessages
//
//===========================================================================
void P_InitKeyMessages()
{
int lastlump, lump, currentnumber = 0;
lastlump = 0;
ClearLocks();
while ((lump = Wads.FindLump ("LOCKDEFS", &lastlump)) != -1)
{
FScanner sc(lump);
while (sc.GetString ())
{
if (sc.Compare("LOCK"))
{
ParseLock(sc, currentnumber);
}
else if (sc.Compare("CLEARLOCKS"))
{
// clear all existing lock definitions and key numbers
ClearLocks();
currentnumber = 0;
}
else
{
sc.ScriptError("Unknown command %s in LockDef", sc.String);
}
}
sc.Close();
}
CreateSortedKeyList();
keysdone = true;
}
//===========================================================================
//
// P_CheckKeys
//
// Returns true if the actor has the required key. If not, a message is
// shown if the actor is also the consoleplayer's camarea, and false is
// returned.
//
//===========================================================================
int P_CheckKeys (AActor *owner, int keynum, bool remote, bool quiet)
{
const char *failtext = NULL;
FSoundID *failsound;
int numfailsounds;
if (owner == NULL) return false;
if (keynum<=0) return true;
// Just a safety precaution. The messages should have been initialized upon game start.
if (!keysdone) P_InitKeyMessages();
FSoundID failage[2] = { "*keytry", "misc/keytry" };
auto lock = Locks.CheckKey(keynum);
if (!lock)
{
if (quiet) return false;
if (keynum == 103 && (gameinfo.flags & GI_SHAREWARE))
failtext = "$TXT_RETAIL_ONLY";
else
failtext = "$TXT_DOES_NOT_WORK";
failsound = failage;
numfailsounds = countof(failage);
}
else
{
if (lock->check(owner)) return true;
if (quiet) return false;
failtext = remote? lock->RemoteMsg : lock->Message;
failsound = &lock->locksound[0];
numfailsounds = lock->locksound.Size();
}
// If we get here, that means the actor isn't holding an appropriate key.
if (owner->CheckLocalView())
{
PrintMessage(failtext);
// Play the first defined key sound.
for (int i = 0; i < numfailsounds; ++i)
{
if (failsound[i] != 0)
{
int snd = S_FindSkinnedSound(owner, failsound[i]);
if (snd != 0)
{
S_Sound (owner, CHAN_VOICE, snd, 1, ATTN_NORM);
break;
}
}
}
}
return false;
}
//==========================================================================
//
// These functions can be used to get color information for
// automap display of keys and locked doors
//
//==========================================================================
int P_GetMapColorForLock (int locknum)
{
if (locknum > 0)
{
auto lock = Locks.CheckKey(locknum);
if (lock) return lock->rgb;
}
return -1;
}
//==========================================================================
//
//
//
//==========================================================================
int P_GetMapColorForKey (AActor * key)
{
decltype(Locks)::Iterator it(Locks);
decltype(Locks)::Pair *pair;
int foundlock = INT_MAX;
int rgb = 0;
while (it.NextPair(pair))
{
if (pair->Key < foundlock && pair->Value.check(key))
{
rgb = pair->Value.rgb;
foundlock = pair->Key;
}
}
return 0;
}
int P_GetKeyTypeCount()
{
return KeyTypes.Size();
}
PClassActor *P_GetKeyType(int num)
{
if ((unsigned)num >= KeyTypes.Size()) return nullptr;
return KeyTypes[num];
}