2016-11-30 09:55:03 +00:00
|
|
|
/*
|
|
|
|
** 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.
|
|
|
|
**---------------------------------------------------------------------------
|
|
|
|
**
|
|
|
|
*/
|
2016-03-01 15:47:10 +00:00
|
|
|
#include "a_keys.h"
|
|
|
|
#include "tarray.h"
|
|
|
|
#include "gi.h"
|
|
|
|
#include "gstrings.h"
|
|
|
|
#include "d_player.h"
|
|
|
|
#include "c_console.h"
|
|
|
|
#include "s_sound.h"
|
|
|
|
#include "sc_man.h"
|
|
|
|
#include "v_palette.h"
|
|
|
|
#include "w_wad.h"
|
|
|
|
#include "doomstat.h"
|
|
|
|
#include "v_font.h"
|
|
|
|
|
|
|
|
|
2016-11-30 09:55:03 +00:00
|
|
|
//===========================================================================
|
|
|
|
//
|
|
|
|
// Data for the LOCKDEFS
|
|
|
|
//
|
|
|
|
//===========================================================================
|
|
|
|
|
|
|
|
//===========================================================================
|
|
|
|
//
|
|
|
|
//
|
|
|
|
//===========================================================================
|
|
|
|
|
2016-03-01 15:47:10 +00:00
|
|
|
struct OneKey
|
|
|
|
{
|
|
|
|
PClassActor *key;
|
|
|
|
int count;
|
|
|
|
|
|
|
|
bool check(AActor *owner)
|
|
|
|
{
|
2017-01-18 14:17:12 +00:00
|
|
|
// 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.
|
|
|
|
AInventory *item;
|
2016-03-01 15:47:10 +00:00
|
|
|
|
2017-01-18 14:17:12 +00:00
|
|
|
for (item = owner->Inventory; item != NULL; item = item->Inventory)
|
|
|
|
{
|
|
|
|
if (item->IsA(key))
|
2016-03-01 15:47:10 +00:00
|
|
|
{
|
2017-01-18 14:17:12 +00:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
else if (item->GetSpecies() == key->TypeName)
|
|
|
|
{
|
|
|
|
return true;
|
2016-03-01 15:47:10 +00:00
|
|
|
}
|
|
|
|
}
|
2017-01-18 14:17:12 +00:00
|
|
|
return false;
|
2016-03-01 15:47:10 +00:00
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2016-11-30 09:55:03 +00:00
|
|
|
//===========================================================================
|
|
|
|
//
|
|
|
|
//
|
|
|
|
//===========================================================================
|
|
|
|
|
2016-03-01 15:47:10 +00:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2016-11-30 09:55:03 +00:00
|
|
|
//===========================================================================
|
|
|
|
//
|
|
|
|
//
|
|
|
|
//===========================================================================
|
|
|
|
|
2016-03-01 15:47:10 +00:00
|
|
|
struct Lock
|
|
|
|
{
|
|
|
|
TArray<Keygroup *> keylist;
|
|
|
|
TArray<FSoundID> locksound;
|
|
|
|
FString Message;
|
|
|
|
FString RemoteMsg;
|
|
|
|
int rgb;
|
|
|
|
|
|
|
|
Lock()
|
|
|
|
{
|
|
|
|
rgb=0;
|
|
|
|
}
|
|
|
|
|
|
|
|
~Lock()
|
|
|
|
{
|
|
|
|
for(unsigned int i=0;i<keylist.Size();i++) delete keylist[i];
|
|
|
|
keylist.Clear();
|
|
|
|
}
|
|
|
|
|
|
|
|
bool check(AActor * owner)
|
|
|
|
{
|
|
|
|
// An empty key list means that any key will do
|
|
|
|
if (!keylist.Size())
|
|
|
|
{
|
2017-01-18 14:17:12 +00:00
|
|
|
auto kt = PClass::FindActor(NAME_Key);
|
2016-03-01 15:47:10 +00:00
|
|
|
for (AInventory * item = owner->Inventory; item != NULL; item = item->Inventory)
|
|
|
|
{
|
2017-01-18 14:17:12 +00:00
|
|
|
if (item->IsKindOf (kt))
|
2016-03-01 15:47:10 +00:00
|
|
|
{
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
else for(unsigned int i=0;i<keylist.Size();i++)
|
|
|
|
{
|
|
|
|
if (!keylist[i]->check(owner)) return false;
|
|
|
|
}
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2016-11-30 09:55:03 +00:00
|
|
|
//===========================================================================
|
|
|
|
//
|
|
|
|
//
|
|
|
|
//===========================================================================
|
2016-03-01 15:47:10 +00:00
|
|
|
|
|
|
|
static Lock *locks[256]; // all valid locks
|
|
|
|
static bool keysdone=false; // have the locks been initialized?
|
|
|
|
static int currentnumber; // number to be assigned to next key
|
|
|
|
static bool ignorekey; // set to true when the current lock is not being used
|
|
|
|
|
|
|
|
static void ClearLocks();
|
|
|
|
|
|
|
|
static const char * keywords_lock[]={
|
|
|
|
"ANY",
|
|
|
|
"MESSAGE",
|
|
|
|
"REMOTEMESSAGE",
|
|
|
|
"MAPCOLOR",
|
|
|
|
"LOCKEDSOUND",
|
|
|
|
NULL
|
|
|
|
};
|
|
|
|
|
|
|
|
//===========================================================================
|
|
|
|
//
|
|
|
|
//
|
|
|
|
//===========================================================================
|
|
|
|
|
|
|
|
static void AddOneKey(Keygroup *keygroup, PClassActor *mi, FScanner &sc)
|
|
|
|
{
|
|
|
|
if (mi)
|
|
|
|
{
|
|
|
|
// Any inventory item can be used to unlock a door
|
|
|
|
if (mi->IsDescendantOf(RUNTIME_CLASS(AInventory)))
|
|
|
|
{
|
|
|
|
OneKey k = {mi,1};
|
|
|
|
keygroup->anykeylist.Push (k);
|
|
|
|
|
|
|
|
//... but only keys get key numbers!
|
2017-02-08 14:47:22 +00:00
|
|
|
if (mi->IsDescendantOf(NAME_Key))
|
2016-03-01 15:47:10 +00:00
|
|
|
{
|
|
|
|
if (!ignorekey &&
|
2017-01-18 14:17:12 +00:00
|
|
|
GetDefaultByType(mi)->special1 == 0)
|
2016-03-01 15:47:10 +00:00
|
|
|
{
|
2017-01-18 14:17:12 +00:00
|
|
|
GetDefaultByType(mi)->special1 = ++currentnumber;
|
2016-03-01 15:47:10 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
sc.ScriptError("'%s' is not an inventory item", sc.String);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
sc.ScriptError("Unknown item '%s'", sc.String);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
//===========================================================================
|
|
|
|
//
|
|
|
|
//
|
|
|
|
//===========================================================================
|
|
|
|
|
|
|
|
static Keygroup *ParseKeygroup(FScanner &sc)
|
|
|
|
{
|
|
|
|
Keygroup *keygroup;
|
|
|
|
PClassActor *mi;
|
|
|
|
|
|
|
|
sc.MustGetStringName("{");
|
|
|
|
keygroup = new Keygroup;
|
|
|
|
while (!sc.CheckString("}"))
|
|
|
|
{
|
|
|
|
sc.MustGetString();
|
|
|
|
mi = PClass::FindActor(sc.String);
|
|
|
|
AddOneKey(keygroup, mi, sc);
|
|
|
|
}
|
|
|
|
if (keygroup->anykeylist.Size() == 0)
|
|
|
|
{
|
|
|
|
delete keygroup;
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
keygroup->anykeylist.ShrinkToFit();
|
|
|
|
return keygroup;
|
|
|
|
}
|
|
|
|
|
|
|
|
//===========================================================================
|
|
|
|
//
|
|
|
|
//
|
|
|
|
//===========================================================================
|
|
|
|
|
|
|
|
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 i,r,g,b;
|
|
|
|
int keynum;
|
|
|
|
Lock sink;
|
|
|
|
Lock *lock = &sink;
|
|
|
|
Keygroup *keygroup;
|
|
|
|
PClassActor *mi;
|
|
|
|
|
|
|
|
sc.MustGetNumber();
|
|
|
|
keynum = sc.Number;
|
|
|
|
|
|
|
|
sc.MustGetString();
|
|
|
|
if (!sc.Compare("{"))
|
|
|
|
{
|
|
|
|
if (!CheckGame(sc.String, false)) keynum = -1;
|
|
|
|
sc.MustGetStringName("{");
|
|
|
|
}
|
|
|
|
|
|
|
|
ignorekey = true;
|
2016-05-18 09:56:39 +00:00
|
|
|
if (keynum > 0 && keynum <= 255)
|
2016-03-01 15:47:10 +00:00
|
|
|
{
|
|
|
|
lock = new Lock;
|
|
|
|
if (locks[keynum])
|
|
|
|
{
|
|
|
|
delete locks[keynum];
|
|
|
|
}
|
|
|
|
locks[keynum] = lock;
|
|
|
|
locks[keynum]->locksound.Push("*keytry");
|
|
|
|
locks[keynum]->locksound.Push("misc/keytry");
|
|
|
|
ignorekey=false;
|
|
|
|
}
|
|
|
|
else if (keynum != -1)
|
|
|
|
{
|
|
|
|
sc.ScriptError("Lock index %d out of range", keynum);
|
|
|
|
}
|
|
|
|
|
|
|
|
while (!sc.CheckString("}"))
|
|
|
|
{
|
|
|
|
sc.MustGetString();
|
|
|
|
switch(i = sc.MatchString(keywords_lock))
|
|
|
|
{
|
|
|
|
case 0: // Any
|
|
|
|
keygroup = ParseKeygroup(sc);
|
|
|
|
if (keygroup)
|
|
|
|
{
|
|
|
|
lock->keylist.Push(keygroup);
|
|
|
|
}
|
|
|
|
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)
|
|
|
|
{
|
|
|
|
keygroup = new Keygroup;
|
|
|
|
AddOneKey(keygroup, mi, sc);
|
|
|
|
if (keygroup)
|
|
|
|
{
|
|
|
|
keygroup->anykeylist.ShrinkToFit();
|
|
|
|
lock->keylist.Push(keygroup);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
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;
|
2017-01-18 14:17:12 +00:00
|
|
|
auto kt = PClass::FindActor(NAME_Key);
|
2016-03-01 15:47:10 +00:00
|
|
|
for(i = 0; i < PClassActor::AllActorClasses.Size(); i++)
|
|
|
|
{
|
2017-01-18 14:17:12 +00:00
|
|
|
if (PClassActor::AllActorClasses[i]->IsDescendantOf(kt))
|
2016-03-01 15:47:10 +00:00
|
|
|
{
|
2017-01-18 14:17:12 +00:00
|
|
|
auto key = GetDefaultByType(PClassActor::AllActorClasses[i]);
|
2016-03-01 15:47:10 +00:00
|
|
|
if (key != NULL)
|
|
|
|
{
|
2017-01-18 14:17:12 +00:00
|
|
|
key->special1 = 0;
|
2016-03-01 15:47:10 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
for(i = 0; i < 256; i++)
|
|
|
|
{
|
|
|
|
if (locks[i] != NULL)
|
|
|
|
{
|
|
|
|
delete locks[i];
|
|
|
|
locks[i] = NULL;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
currentnumber = 0;
|
|
|
|
keysdone = false;
|
|
|
|
}
|
|
|
|
|
|
|
|
//===========================================================================
|
|
|
|
//
|
|
|
|
// P_InitKeyMessages
|
|
|
|
//
|
|
|
|
//===========================================================================
|
|
|
|
|
|
|
|
void P_InitKeyMessages()
|
|
|
|
{
|
|
|
|
int lastlump, lump;
|
|
|
|
|
|
|
|
lastlump = 0;
|
|
|
|
|
|
|
|
ClearLocks();
|
|
|
|
while ((lump = Wads.FindLump ("LOCKDEFS", &lastlump)) != -1)
|
|
|
|
{
|
|
|
|
FScanner sc(lump);
|
|
|
|
while (sc.GetString ())
|
|
|
|
{
|
|
|
|
if (sc.Compare("LOCK"))
|
|
|
|
{
|
|
|
|
ParseLock(sc);
|
|
|
|
}
|
|
|
|
else if (sc.Compare("CLEARLOCKS"))
|
|
|
|
{
|
|
|
|
// clear all existing lock definitions and key numbers
|
|
|
|
ClearLocks();
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
sc.ScriptError("Unknown command %s in LockDef", sc.String);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
sc.Close();
|
|
|
|
}
|
|
|
|
keysdone = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
//===========================================================================
|
|
|
|
//
|
|
|
|
// P_DeinitKeyMessages
|
|
|
|
//
|
|
|
|
//===========================================================================
|
|
|
|
|
|
|
|
void P_DeinitKeyMessages()
|
|
|
|
{
|
|
|
|
ClearLocks();
|
|
|
|
}
|
|
|
|
|
|
|
|
//===========================================================================
|
|
|
|
//
|
|
|
|
// 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.
|
|
|
|
//
|
|
|
|
//===========================================================================
|
|
|
|
|
|
|
|
bool P_CheckKeys (AActor *owner, int keynum, bool remote)
|
|
|
|
{
|
|
|
|
const char *failtext = NULL;
|
|
|
|
FSoundID *failsound;
|
|
|
|
int numfailsounds;
|
|
|
|
|
|
|
|
if (owner == NULL) return false;
|
|
|
|
if (keynum<=0 || keynum>255) 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" };
|
|
|
|
|
|
|
|
if (!locks[keynum])
|
|
|
|
{
|
|
|
|
if (keynum == 103 && (gameinfo.flags & GI_SHAREWARE))
|
|
|
|
failtext = "$TXT_RETAIL_ONLY";
|
|
|
|
else
|
|
|
|
failtext = "$TXT_DOES_NOT_WORK";
|
|
|
|
|
|
|
|
failsound = failage;
|
|
|
|
numfailsounds = countof(failage);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
if (locks[keynum]->check(owner)) return true;
|
|
|
|
failtext = remote? locks[keynum]->RemoteMsg : locks[keynum]->Message;
|
|
|
|
failsound = &locks[keynum]->locksound[0];
|
|
|
|
numfailsounds = locks[keynum]->locksound.Size();
|
|
|
|
}
|
|
|
|
|
|
|
|
// If we get here, that means the actor isn't holding an appropriate key.
|
|
|
|
|
|
|
|
if (owner == players[consoleplayer].camera)
|
|
|
|
{
|
|
|
|
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 lock)
|
|
|
|
{
|
|
|
|
if (lock > 0 && lock < 256)
|
|
|
|
{
|
|
|
|
if (locks[lock]) return locks[lock]->rgb;
|
|
|
|
}
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
//==========================================================================
|
|
|
|
//
|
|
|
|
//
|
|
|
|
//
|
|
|
|
//==========================================================================
|
|
|
|
|
|
|
|
int P_GetMapColorForKey (AInventory * key)
|
|
|
|
{
|
|
|
|
int i;
|
|
|
|
|
|
|
|
for (i = 0; i < 256; i++)
|
|
|
|
{
|
|
|
|
if (locks[i] && locks[i]->check(key)) return locks[i]->rgb;
|
|
|
|
}
|
|
|
|
return 0;
|
|
|
|
}
|