2006-02-24 04:48:15 +00:00
|
|
|
/*
|
|
|
|
** d_dehacked.cpp
|
|
|
|
** Parses dehacked/bex patches and changes game structures accordingly
|
|
|
|
**
|
|
|
|
**---------------------------------------------------------------------------
|
2006-06-11 01:37:00 +00:00
|
|
|
** Copyright 1998-2006 Randy Heit
|
2006-02-24 04:48:15 +00:00
|
|
|
** 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.
|
|
|
|
**---------------------------------------------------------------------------
|
|
|
|
**
|
|
|
|
** Much of this file is fudging code to compensate for the fact that most of
|
|
|
|
** what could be changed with Dehacked is no longer in the same state it was
|
|
|
|
** in as of Doom 1.9. There is a lump in zdoom.wad (DEHSUPP) that stores most
|
|
|
|
** of the lookup tables used so that they need not be loaded all the time.
|
|
|
|
** Also, their total size is less in the lump than when they were part of the
|
|
|
|
** executable, so it saves space on disk as well as in memory.
|
|
|
|
*/
|
|
|
|
|
|
|
|
#include <stdio.h>
|
|
|
|
#include <stdlib.h>
|
|
|
|
#include <string.h>
|
|
|
|
#include <stddef.h>
|
|
|
|
|
|
|
|
#include "doomtype.h"
|
|
|
|
#include "templates.h"
|
|
|
|
#include "doomstat.h"
|
|
|
|
#include "info.h"
|
|
|
|
#include "d_dehacked.h"
|
|
|
|
#include "s_sound.h"
|
|
|
|
#include "g_level.h"
|
|
|
|
#include "cmdlib.h"
|
|
|
|
#include "gstrings.h"
|
|
|
|
#include "m_alloc.h"
|
|
|
|
#include "m_misc.h"
|
|
|
|
#include "w_wad.h"
|
|
|
|
#include "d_player.h"
|
|
|
|
#include "r_state.h"
|
|
|
|
#include "gi.h"
|
|
|
|
#include "c_dispatch.h"
|
|
|
|
#include "decallib.h"
|
|
|
|
#include "r_draw.h"
|
|
|
|
#include "v_palette.h"
|
2006-04-11 16:27:41 +00:00
|
|
|
#include "a_sharedglobal.h"
|
2006-02-24 04:48:15 +00:00
|
|
|
|
|
|
|
// [SO] Just the way Randy said to do it :)
|
|
|
|
// [RH] Made this CVAR_SERVERINFO
|
|
|
|
CVAR (Int, infighting, 0, CVAR_SERVERINFO)
|
|
|
|
|
|
|
|
static bool LoadDehSupp ();
|
|
|
|
static void UnloadDehSupp ();
|
|
|
|
|
|
|
|
// Action functions available to patches
|
|
|
|
struct CodePtrMap
|
|
|
|
{
|
|
|
|
short name;
|
|
|
|
WORD num;
|
|
|
|
};
|
|
|
|
|
|
|
|
static CodePtrMap *CodePtrNames;
|
|
|
|
static int NumCodePtrs;
|
|
|
|
|
|
|
|
// Prototype the dehacked code pointers
|
|
|
|
#define WEAPON(x) void A_##x(AActor*);
|
|
|
|
#define ACTOR(x) void A_##x(AActor*);
|
|
|
|
#include "d_dehackedactions.h"
|
|
|
|
|
|
|
|
// Declare the dehacked code pointers
|
|
|
|
static const actionf_p CodePtrs[] =
|
|
|
|
{
|
|
|
|
NULL,
|
|
|
|
#define WEAPON(x) A_##x,
|
|
|
|
#define ACTOR(x) A_##x,
|
|
|
|
#include "d_dehackedactions.h"
|
|
|
|
};
|
|
|
|
|
|
|
|
static const char *const AmmoNames[12] =
|
|
|
|
{
|
|
|
|
"Clip",
|
|
|
|
"Shell",
|
|
|
|
"Cell",
|
|
|
|
"RocketAmmo",
|
|
|
|
"GoldWandAmmo",
|
|
|
|
NULL,
|
|
|
|
"BlasterAmmo",
|
|
|
|
"SkullRodAmmo",
|
|
|
|
"PhoenixRodAmmo",
|
|
|
|
"MaceAmmo",
|
|
|
|
"Mana1",
|
|
|
|
"Mana2"
|
|
|
|
};
|
|
|
|
|
|
|
|
static const char *const WeaponNames[9] =
|
|
|
|
{
|
|
|
|
"Fist",
|
|
|
|
"Pistol",
|
|
|
|
"Shotgun",
|
|
|
|
"Chaingun",
|
|
|
|
"RocketLauncher",
|
|
|
|
"PlasmaRifle",
|
|
|
|
"BFG9000",
|
|
|
|
"Chainsaw",
|
|
|
|
"SuperShotgun"
|
|
|
|
};
|
|
|
|
|
|
|
|
// Miscellaneous info that used to be constant
|
|
|
|
DehInfo deh =
|
|
|
|
{
|
|
|
|
100, // .StartHealth
|
|
|
|
50, // .StartBullets
|
2006-04-20 14:21:27 +00:00
|
|
|
-1, // .MaxHealth
|
2006-02-24 04:48:15 +00:00
|
|
|
200, // .MaxArmor
|
|
|
|
1, // .GreenAC
|
|
|
|
2, // .BlueAC
|
|
|
|
200, // .MaxSoulsphere
|
|
|
|
100, // .SoulsphereHealth
|
|
|
|
200, // .MegasphereHealth
|
|
|
|
100, // .GodHealth
|
|
|
|
200, // .FAArmor
|
|
|
|
2, // .FAAC
|
|
|
|
200, // .KFAArmor
|
|
|
|
2, // .KFAAC
|
|
|
|
"PLAY", // Name of player sprite
|
|
|
|
255, // Rocket explosion style, 255=use cvar
|
|
|
|
FRACUNIT*2/3, // Rocket explosion alpha
|
|
|
|
false, // .NoAutofreeze
|
|
|
|
};
|
|
|
|
|
|
|
|
// Doom identified pickup items by their sprites. ZDoom prefers to use their
|
|
|
|
// class type to identify them instead. To support the traditional Doom
|
|
|
|
// behavior, for every thing touched by dehacked that has the MF_PICKUP flag,
|
|
|
|
// a new subclass of ADehackedPickup will be created with properties copied
|
|
|
|
// from the original actor's defaults. The original actor is then changed to
|
|
|
|
// spawn the new class.
|
|
|
|
|
|
|
|
void A_SpawnDehackedPickup (AActor *);
|
|
|
|
|
|
|
|
class ADehackedPickup : public AInventory
|
|
|
|
{
|
|
|
|
DECLARE_ACTOR (ADehackedPickup, AInventory)
|
|
|
|
HAS_OBJECT_POINTERS
|
|
|
|
public:
|
|
|
|
void Destroy ();
|
|
|
|
const char *PickupMessage ();
|
|
|
|
bool ShouldRespawn ();
|
|
|
|
bool ShouldStay ();
|
|
|
|
bool TryPickup (AActor *toucher);
|
|
|
|
void PlayPickupSound (AActor *toucher);
|
|
|
|
void DoPickupSpecial (AActor *toucher);
|
|
|
|
private:
|
2006-05-10 02:40:43 +00:00
|
|
|
const PClass *DetermineType ();
|
2006-02-24 04:48:15 +00:00
|
|
|
AInventory *RealPickup;
|
|
|
|
};
|
|
|
|
|
|
|
|
IMPLEMENT_POINTY_CLASS (ADehackedPickup)
|
|
|
|
DECLARE_POINTER (RealPickup)
|
|
|
|
END_POINTERS
|
|
|
|
|
|
|
|
FState ADehackedPickup::States[] =
|
|
|
|
{
|
|
|
|
S_NORMAL(TNT1, 0, 0, NULL, &States[1]),
|
|
|
|
S_NORMAL(TNT1, 0, 0, A_SpawnDehackedPickup, NULL)
|
|
|
|
};
|
|
|
|
|
|
|
|
BEGIN_DEFAULTS (ADehackedPickup, Any, -1, 0)
|
|
|
|
END_DEFAULTS
|
|
|
|
|
2006-05-10 02:40:43 +00:00
|
|
|
TArray<PClass *> DehackedPickups;
|
|
|
|
TArray<PClass *> TouchedActors;
|
2006-02-24 04:48:15 +00:00
|
|
|
|
|
|
|
char *UnchangedSpriteNames;
|
|
|
|
int NumUnchangedSprites;
|
|
|
|
|
|
|
|
// Sprite<->Class map for ADehackedPickup::DetermineType
|
|
|
|
static struct DehSpriteMap
|
|
|
|
{
|
|
|
|
char Sprite[5];
|
|
|
|
const char *ClassName;
|
|
|
|
}
|
|
|
|
DehSpriteMappings[] =
|
|
|
|
{
|
|
|
|
{ "AMMO", "ClipBox" },
|
|
|
|
{ "ARM1", "GreenArmor" },
|
|
|
|
{ "ARM2", "BlueArmor" },
|
|
|
|
{ "BFUG", "BFG9000" },
|
|
|
|
{ "BKEY", "BlueCard" },
|
|
|
|
{ "BON1", "HealthBonus" },
|
|
|
|
{ "BON2", "ArmorBonus" },
|
|
|
|
{ "BPAK", "Backpack" },
|
|
|
|
{ "BROK", "RocketBox" },
|
|
|
|
{ "BSKU", "BlueSkull" },
|
|
|
|
{ "CELL", "Cell" },
|
|
|
|
{ "CELP", "CellPack" },
|
|
|
|
{ "CLIP", "Clip" },
|
|
|
|
{ "CSAW", "Chainsaw" },
|
|
|
|
{ "LAUN", "RocketLauncher" },
|
|
|
|
{ "MEDI", "Medikit" },
|
|
|
|
{ "MEGA", "Megasphere" },
|
|
|
|
{ "MGUN", "Chaingun" },
|
|
|
|
{ "PINS", "BlurSphere" },
|
|
|
|
{ "PINV", "InvulnerabilitySphere" },
|
|
|
|
{ "PLAS", "PlasmaRifle" },
|
|
|
|
{ "PMAP", "Allmap" },
|
|
|
|
{ "PSTR", "Berserk" },
|
|
|
|
{ "PVIS", "Infrared" },
|
|
|
|
{ "RKEY", "RedCard" },
|
|
|
|
{ "ROCK", "RocketAmmo" },
|
|
|
|
{ "RSKU", "RedSkull" },
|
|
|
|
{ "SBOX", "ShellBox" },
|
|
|
|
{ "SGN2", "SuperShotgun" },
|
|
|
|
{ "SHEL", "Shell" },
|
|
|
|
{ "SHOT", "Shotgun" },
|
|
|
|
{ "SOUL", "Soulsphere" },
|
|
|
|
{ "STIM", "Stimpack" },
|
|
|
|
{ "SUIT", "RadSuit" },
|
|
|
|
{ "YKEY", "YellowCard" },
|
|
|
|
{ "YSKU", "YellowSkull" }
|
|
|
|
};
|
|
|
|
|
|
|
|
#define LINESIZE 2048
|
|
|
|
|
|
|
|
#define CHECKKEY(a,b) if (!stricmp (Line1, (a))) (b) = atoi(Line2);
|
|
|
|
|
|
|
|
static char *PatchFile, *PatchPt, *PatchName;
|
|
|
|
static char *Line1, *Line2;
|
|
|
|
static int dversion, pversion;
|
|
|
|
static BOOL including, includenotext;
|
|
|
|
|
|
|
|
static const char *unknown_str = "Unknown key %s encountered in %s %d.\n";
|
|
|
|
|
|
|
|
static FStringTable *EnglishStrings;
|
|
|
|
|
|
|
|
// This is an offset to be used for computing the text stuff.
|
|
|
|
// Straight from the DeHackEd source which was
|
|
|
|
// Written by Greg Lewis, gregl@umich.edu.
|
|
|
|
static int toff[] = {129044, 129044, 129044, 129284, 129380};
|
|
|
|
|
|
|
|
// Every string in DEHSUPP appears in the name table. The name table
|
|
|
|
// is always in sorted order.
|
|
|
|
static WORD *NameOffs;
|
|
|
|
static char *NameBase;
|
|
|
|
static int NumNames;
|
|
|
|
|
|
|
|
// These are the original heights of every Doom 2 thing. They are used if a patch
|
|
|
|
// specifies that a thing should be hanging from the ceiling but doesn't specify
|
|
|
|
// a height for the thing, since these are the heights it probably wants.
|
|
|
|
static byte *OrgHeights;
|
|
|
|
static int NumOrgHeights;
|
|
|
|
|
|
|
|
// This is a list of all the action functions used by each of Doom's states.
|
|
|
|
static BYTE *ActionList;
|
|
|
|
static int NumActions;
|
|
|
|
|
|
|
|
// DeHackEd made the erroneous assumption that if a state didn't appear in
|
|
|
|
// Doom with an action function, then it was incorrect to assign it one.
|
|
|
|
// This is a list of the states that had action functions, so we can figure
|
|
|
|
// out where in the original list of states a DeHackEd codepointer is.
|
|
|
|
// (DeHackEd might also have done this for compatibility between Doom
|
|
|
|
// versions, because states could move around, but actions would never
|
|
|
|
// disappear, but that doesn't explain why frame patches specify an exact
|
|
|
|
// state rather than a code pointer.)
|
|
|
|
static short *CodePConv;
|
|
|
|
static int NumCodeP;
|
|
|
|
|
|
|
|
// Sprite names in the order Doom originally had them.
|
|
|
|
static char **OrgSprNames;
|
|
|
|
static int NumSprites;
|
|
|
|
|
|
|
|
// Map to where the orginal Doom states have moved to
|
|
|
|
enum EStateBase
|
|
|
|
{
|
|
|
|
FirstState,
|
|
|
|
SpawnState,
|
|
|
|
DeathState
|
|
|
|
};
|
|
|
|
|
|
|
|
struct StateMapper
|
|
|
|
{
|
|
|
|
FState *State;
|
|
|
|
int StateSpan;
|
2006-05-10 02:40:43 +00:00
|
|
|
const PClass *Owner;
|
2006-02-24 04:48:15 +00:00
|
|
|
bool OwnerIsPickup;
|
|
|
|
};
|
|
|
|
|
|
|
|
static StateMapper *StateMap;
|
|
|
|
static int NumStateMaps;
|
|
|
|
|
|
|
|
// Render styles
|
|
|
|
struct StyleName
|
|
|
|
{
|
|
|
|
short Name;
|
|
|
|
BYTE Num;
|
|
|
|
};
|
|
|
|
|
|
|
|
static StyleName *StyleNames;
|
|
|
|
static int NumStyleNames;
|
|
|
|
|
|
|
|
// Sound equivalences. When a patch tries to change a sound,
|
|
|
|
// use these sound names.
|
|
|
|
static short *SoundMap;
|
|
|
|
static int NumSounds;
|
|
|
|
|
|
|
|
// Names of different actor types, in original Doom 2 order
|
|
|
|
static short *InfoNames;
|
|
|
|
static int NumInfos;
|
|
|
|
|
|
|
|
// bit flags for PatchThing (a .bex extension):
|
|
|
|
struct BitName
|
|
|
|
{
|
|
|
|
short Name;
|
|
|
|
BYTE Bit;
|
|
|
|
BYTE WhichFlags;
|
|
|
|
};
|
|
|
|
|
|
|
|
static BitName *BitNames;
|
|
|
|
static int NumBitNames;
|
|
|
|
|
|
|
|
struct Key {
|
|
|
|
char *name;
|
|
|
|
ptrdiff_t offset;
|
|
|
|
};
|
|
|
|
|
|
|
|
static int PatchThing (int);
|
|
|
|
static int PatchSound (int);
|
|
|
|
static int PatchFrame (int);
|
|
|
|
static int PatchSprite (int);
|
|
|
|
static int PatchAmmo (int);
|
|
|
|
static int PatchWeapon (int);
|
|
|
|
static int PatchPointer (int);
|
|
|
|
static int PatchCheats (int);
|
|
|
|
static int PatchMisc (int);
|
|
|
|
static int PatchText (int);
|
|
|
|
static int PatchStrings (int);
|
|
|
|
static int PatchPars (int);
|
|
|
|
static int PatchCodePtrs (int);
|
|
|
|
static int DoInclude (int);
|
|
|
|
|
|
|
|
static const struct {
|
|
|
|
char *name;
|
|
|
|
int (*func)(int);
|
|
|
|
} Modes[] = {
|
|
|
|
// These appear in .deh and .bex files
|
|
|
|
{ "Thing", PatchThing },
|
|
|
|
{ "Sound", PatchSound },
|
|
|
|
{ "Frame", PatchFrame },
|
|
|
|
{ "Sprite", PatchSprite },
|
|
|
|
{ "Ammo", PatchAmmo },
|
|
|
|
{ "Weapon", PatchWeapon },
|
|
|
|
{ "Pointer", PatchPointer },
|
|
|
|
{ "Cheat", PatchCheats },
|
|
|
|
{ "Misc", PatchMisc },
|
|
|
|
{ "Text", PatchText },
|
|
|
|
// These appear in .bex files
|
|
|
|
{ "include", DoInclude },
|
|
|
|
{ "[STRINGS]", PatchStrings },
|
|
|
|
{ "[PARS]", PatchPars },
|
|
|
|
{ "[CODEPTR]", PatchCodePtrs },
|
|
|
|
{ NULL, },
|
|
|
|
};
|
|
|
|
|
|
|
|
static int HandleMode (const char *mode, int num);
|
|
|
|
static BOOL HandleKey (const struct Key *keys, void *structure, const char *key, int value);
|
|
|
|
static BOOL ReadChars (char **stuff, int size);
|
|
|
|
static char *igets (void);
|
|
|
|
static int GetLine (void);
|
|
|
|
|
|
|
|
inline const char *GetName (int name)
|
|
|
|
{
|
|
|
|
return NameBase + NameOffs[name];
|
|
|
|
}
|
|
|
|
|
|
|
|
// Names are conveniently stored in sorted order
|
|
|
|
int FindName (const char *name)
|
|
|
|
{
|
|
|
|
int min = 0;
|
|
|
|
int max = NumNames - 1;
|
|
|
|
|
|
|
|
while (min <= max)
|
|
|
|
{
|
|
|
|
int mid = (min + max) / 2;
|
|
|
|
int lexx = stricmp (GetName (mid), name);
|
|
|
|
if (lexx == 0)
|
|
|
|
{
|
|
|
|
return mid;
|
|
|
|
}
|
|
|
|
else if (lexx < 0)
|
|
|
|
{
|
|
|
|
min = mid + 1;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
max = mid - 1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int HandleMode (const char *mode, int num)
|
|
|
|
{
|
|
|
|
int i = 0;
|
|
|
|
|
|
|
|
while (Modes[i].name && stricmp (Modes[i].name, mode))
|
|
|
|
i++;
|
|
|
|
|
|
|
|
if (Modes[i].name)
|
|
|
|
return Modes[i].func (num);
|
|
|
|
|
|
|
|
// Handle unknown or unimplemented data
|
|
|
|
Printf ("Unknown chunk %s encountered. Skipping.\n", mode);
|
|
|
|
do
|
|
|
|
i = GetLine ();
|
|
|
|
while (i == 1);
|
|
|
|
|
|
|
|
return i;
|
|
|
|
}
|
|
|
|
|
|
|
|
static BOOL HandleKey (const struct Key *keys, void *structure, const char *key, int value)
|
|
|
|
{
|
|
|
|
while (keys->name && stricmp (keys->name, key))
|
|
|
|
keys++;
|
|
|
|
|
|
|
|
if (keys->name) {
|
|
|
|
*((int *)(((byte *)structure) + keys->offset)) = value;
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int FindSprite (const char *sprname)
|
|
|
|
{
|
|
|
|
int i;
|
|
|
|
DWORD nameint = *((DWORD *)sprname);
|
|
|
|
|
|
|
|
for (i = 0; i < NumUnchangedSprites; ++i)
|
|
|
|
{
|
|
|
|
if (*((DWORD *)&UnchangedSpriteNames[i*4]) == nameint)
|
|
|
|
{
|
|
|
|
return i;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
static FState *FindState (int statenum)
|
|
|
|
{
|
|
|
|
int i;
|
|
|
|
int stateacc;
|
|
|
|
|
|
|
|
if (statenum == 0)
|
|
|
|
return NULL;
|
|
|
|
|
|
|
|
for (i = 0, stateacc = 1; i < NumStateMaps; i++)
|
|
|
|
{
|
|
|
|
if (stateacc <= statenum && stateacc + StateMap[i].StateSpan > statenum)
|
|
|
|
{
|
|
|
|
if (StateMap[i].OwnerIsPickup)
|
|
|
|
{
|
2006-05-10 02:40:43 +00:00
|
|
|
TouchedActors.Push (const_cast<PClass *>(StateMap[i].Owner));
|
2006-02-24 04:48:15 +00:00
|
|
|
}
|
|
|
|
return StateMap[i].State + statenum - stateacc;
|
|
|
|
}
|
|
|
|
stateacc += StateMap[i].StateSpan;
|
|
|
|
}
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
int FindStyle (const char *namestr)
|
|
|
|
{
|
|
|
|
int min = 0;
|
|
|
|
int max = NumStyleNames - 1;
|
|
|
|
int name = FindName (Line2);
|
|
|
|
if (name != -1)
|
|
|
|
{
|
|
|
|
while (min <= max)
|
|
|
|
{
|
|
|
|
int mid = (min + max) / 2;
|
|
|
|
if (StyleNames[mid].Name == name)
|
|
|
|
{
|
|
|
|
return StyleNames[mid].Num;
|
|
|
|
}
|
|
|
|
else if (StyleNames[mid].Name < name)
|
|
|
|
{
|
|
|
|
min = mid + 1;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
max = mid - 1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
DPrintf("Unknown render style %s\n", namestr);
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
static BOOL ReadChars (char **stuff, int size)
|
|
|
|
{
|
|
|
|
char *str = *stuff;
|
|
|
|
|
|
|
|
if (!size) {
|
|
|
|
*str = 0;
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
do {
|
|
|
|
// Ignore carriage returns
|
|
|
|
if (*PatchPt != '\r')
|
|
|
|
*str++ = *PatchPt;
|
|
|
|
else
|
|
|
|
size++;
|
|
|
|
|
|
|
|
PatchPt++;
|
|
|
|
} while (--size);
|
|
|
|
|
|
|
|
*str = 0;
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void ReplaceSpecialChars (char *str)
|
|
|
|
{
|
|
|
|
char *p = str, c;
|
|
|
|
int i;
|
|
|
|
|
|
|
|
while ( (c = *p++) ) {
|
|
|
|
if (c != '\\') {
|
|
|
|
*str++ = c;
|
|
|
|
} else {
|
|
|
|
switch (*p) {
|
|
|
|
case 'n':
|
|
|
|
case 'N':
|
|
|
|
*str++ = '\n';
|
|
|
|
break;
|
|
|
|
case 't':
|
|
|
|
case 'T':
|
|
|
|
*str++ = '\t';
|
|
|
|
break;
|
|
|
|
case 'r':
|
|
|
|
case 'R':
|
|
|
|
*str++ = '\r';
|
|
|
|
break;
|
|
|
|
case 'x':
|
|
|
|
case 'X':
|
|
|
|
c = 0;
|
|
|
|
p++;
|
|
|
|
for (i = 0; i < 2; i++) {
|
|
|
|
c <<= 4;
|
|
|
|
if (*p >= '0' && *p <= '9')
|
|
|
|
c += *p-'0';
|
|
|
|
else if (*p >= 'a' && *p <= 'f')
|
|
|
|
c += 10 + *p-'a';
|
|
|
|
else if (*p >= 'A' && *p <= 'F')
|
|
|
|
c += 10 + *p-'A';
|
|
|
|
else
|
|
|
|
break;
|
|
|
|
p++;
|
|
|
|
}
|
|
|
|
*str++ = c;
|
|
|
|
break;
|
|
|
|
case '0':
|
|
|
|
case '1':
|
|
|
|
case '2':
|
|
|
|
case '3':
|
|
|
|
case '4':
|
|
|
|
case '5':
|
|
|
|
case '6':
|
|
|
|
case '7':
|
|
|
|
c = 0;
|
|
|
|
for (i = 0; i < 3; i++) {
|
|
|
|
c <<= 3;
|
|
|
|
if (*p >= '0' && *p <= '7')
|
|
|
|
c += *p-'0';
|
|
|
|
else
|
|
|
|
break;
|
|
|
|
p++;
|
|
|
|
}
|
|
|
|
*str++ = c;
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
*str++ = *p;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
p++;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
*str = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static char *skipwhite (char *str)
|
|
|
|
{
|
|
|
|
if (str)
|
|
|
|
while (*str && isspace(*str))
|
|
|
|
str++;
|
|
|
|
return str;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void stripwhite (char *str)
|
|
|
|
{
|
|
|
|
char *end = str + strlen(str) - 1;
|
|
|
|
|
|
|
|
while (end >= str && isspace(*end))
|
|
|
|
end--;
|
|
|
|
|
|
|
|
end[1] = '\0';
|
|
|
|
}
|
|
|
|
|
|
|
|
static char *igets (void)
|
|
|
|
{
|
|
|
|
char *line;
|
|
|
|
|
|
|
|
if (*PatchPt == '\0')
|
|
|
|
return NULL;
|
|
|
|
|
|
|
|
line = PatchPt;
|
|
|
|
|
|
|
|
while (*PatchPt != '\n' && *PatchPt != '\0')
|
|
|
|
PatchPt++;
|
|
|
|
|
|
|
|
if (*PatchPt == '\n')
|
|
|
|
*PatchPt++ = 0;
|
|
|
|
|
|
|
|
return line;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int GetLine (void)
|
|
|
|
{
|
|
|
|
char *line, *line2;
|
|
|
|
|
|
|
|
do {
|
|
|
|
while ( (line = igets ()) )
|
|
|
|
if (line[0] != '#') // Skip comment lines
|
|
|
|
break;
|
|
|
|
|
|
|
|
if (!line)
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
Line1 = skipwhite (line);
|
|
|
|
} while (Line1 && *Line1 == 0); // Loop until we get a line with
|
|
|
|
// more than just whitespace.
|
|
|
|
line = strchr (Line1, '=');
|
|
|
|
|
|
|
|
if (line) { // We have an '=' in the input line
|
|
|
|
line2 = line;
|
|
|
|
while (--line2 >= Line1)
|
|
|
|
if (*line2 > ' ')
|
|
|
|
break;
|
|
|
|
|
|
|
|
if (line2 < Line1)
|
|
|
|
return 0; // Nothing before '='
|
|
|
|
|
|
|
|
*(line2 + 1) = 0;
|
|
|
|
|
|
|
|
line++;
|
|
|
|
while (*line && *line <= ' ')
|
|
|
|
line++;
|
|
|
|
|
|
|
|
if (*line == 0)
|
|
|
|
return 0; // Nothing after '='
|
|
|
|
|
|
|
|
Line2 = line;
|
|
|
|
|
|
|
|
return 1;
|
|
|
|
} else { // No '=' in input line
|
|
|
|
line = Line1 + 1;
|
|
|
|
while (*line > ' ')
|
|
|
|
line++; // Get beyond first word
|
|
|
|
|
|
|
|
*line++ = 0;
|
|
|
|
while (*line && *line <= ' ')
|
|
|
|
line++; // Skip white space
|
|
|
|
|
|
|
|
//.bex files don't have this restriction
|
|
|
|
//if (*line == 0)
|
|
|
|
// return 0; // No second word
|
|
|
|
|
|
|
|
Line2 = line;
|
|
|
|
|
|
|
|
return 2;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static int PatchThing (int thingy)
|
|
|
|
{
|
|
|
|
enum
|
|
|
|
{
|
|
|
|
MF_TRANSLATION = 0x0c000000, // if 0x4 0x8 or 0xc, use a translation
|
|
|
|
MF_TRANSSHIFT = 26, // table for player colormaps
|
|
|
|
};
|
|
|
|
|
|
|
|
int result;
|
|
|
|
AActor *info, dummy;
|
|
|
|
bool hadHeight = false;
|
|
|
|
bool hadTranslucency = false;
|
|
|
|
bool hadStyle = false;
|
|
|
|
int oldflags;
|
2006-05-10 02:40:43 +00:00
|
|
|
const PClass *type;
|
2006-02-24 04:48:15 +00:00
|
|
|
SWORD *ednum, dummyed;
|
|
|
|
|
|
|
|
type = NULL;
|
|
|
|
info = &dummy;
|
|
|
|
ednum = &dummyed;
|
|
|
|
if (thingy > NumInfos || thingy <= 0)
|
|
|
|
{
|
|
|
|
Printf ("Thing %d out of range.\n", thingy);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
DPrintf ("Thing %d\n", thingy);
|
|
|
|
if (thingy > 0)
|
|
|
|
{
|
2006-05-10 02:40:43 +00:00
|
|
|
type = PClass::FindClass (GetName (InfoNames[thingy - 1]));
|
2006-02-24 04:48:15 +00:00
|
|
|
if (type == NULL)
|
|
|
|
{
|
|
|
|
info = &dummy;
|
|
|
|
ednum = &dummyed;
|
|
|
|
Printf ("Could not find thing %s (index %d)\n",
|
|
|
|
GetName (InfoNames[thingy - 1]), thingy);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
info = GetDefaultByType (type);
|
|
|
|
ednum = &type->ActorInfo->DoomEdNum;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
oldflags = info->flags;
|
|
|
|
|
|
|
|
while ((result = GetLine ()) == 1)
|
|
|
|
{
|
|
|
|
char *endptr;
|
|
|
|
unsigned long val = strtoul (Line2, &endptr, 10);
|
|
|
|
size_t linelen = strlen (Line1);
|
|
|
|
|
|
|
|
if (linelen == 10 && stricmp (Line1, "Hit points") == 0)
|
|
|
|
{
|
|
|
|
info->health = val;
|
|
|
|
}
|
|
|
|
else if (linelen == 13 && stricmp (Line1, "Reaction time") == 0)
|
|
|
|
{
|
|
|
|
info->reactiontime = val;
|
|
|
|
}
|
|
|
|
else if (linelen == 11 && stricmp (Line1, "Pain chance") == 0)
|
|
|
|
{
|
|
|
|
info->PainChance = val;
|
|
|
|
}
|
|
|
|
else if (linelen == 12 && stricmp (Line1, "Translucency") == 0)
|
|
|
|
{
|
|
|
|
info->alpha = val;
|
|
|
|
info->RenderStyle = STYLE_Translucent;
|
|
|
|
hadTranslucency = true;
|
|
|
|
hadStyle = true;
|
|
|
|
}
|
|
|
|
else if (linelen == 6 && stricmp (Line1, "Height") == 0)
|
|
|
|
{
|
|
|
|
info->height = val;
|
|
|
|
hadHeight = true;
|
|
|
|
}
|
|
|
|
else if (linelen == 14 && stricmp (Line1, "Missile damage") == 0)
|
|
|
|
{
|
|
|
|
info->damage = val;
|
|
|
|
}
|
|
|
|
else if (linelen == 5)
|
|
|
|
{
|
|
|
|
if (stricmp (Line1, "Speed") == 0)
|
|
|
|
{
|
|
|
|
info->Speed = val;
|
|
|
|
}
|
|
|
|
else if (stricmp (Line1, "Width") == 0)
|
|
|
|
{
|
|
|
|
info->radius = val;
|
|
|
|
}
|
|
|
|
else if (stricmp (Line1, "Alpha") == 0)
|
|
|
|
{
|
|
|
|
info->alpha = (fixed_t)(atof (Line2) * FRACUNIT);
|
|
|
|
hadTranslucency = true;
|
|
|
|
}
|
|
|
|
else if (stricmp (Line1, "Scale") == 0)
|
|
|
|
{
|
|
|
|
info->xscale = clamp ((int)(atof (Line2) * 64), 1, 256) - 1;
|
|
|
|
info->yscale = clamp ((int)(atof (Line2) * 64), 1, 256) - 1;
|
|
|
|
}
|
|
|
|
else if (stricmp (Line1, "Decal") == 0)
|
|
|
|
{
|
|
|
|
stripwhite (Line2);
|
2006-04-12 01:50:09 +00:00
|
|
|
const FDecalTemplate *decal = DecalLibrary.GetDecalByName (Line2);
|
2006-02-24 04:48:15 +00:00
|
|
|
if (decal != NULL)
|
|
|
|
{
|
2006-04-12 01:50:09 +00:00
|
|
|
info->DecalGenerator = const_cast <FDecalTemplate *>(decal);
|
2006-02-24 04:48:15 +00:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
Printf ("Thing %d: Unknown decal %s\n", thingy, Line2);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else if (linelen == 12 && stricmp (Line1, "Render Style") == 0)
|
|
|
|
{
|
|
|
|
stripwhite (Line2);
|
|
|
|
int style = FindStyle (Line2);
|
|
|
|
if (style >= 0)
|
|
|
|
{
|
|
|
|
info->RenderStyle = style;
|
|
|
|
hadStyle = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else if (linelen > 6)
|
|
|
|
{
|
|
|
|
if (linelen == 12 && stricmp (Line1, "No Ice Death") == 0)
|
|
|
|
{
|
|
|
|
if (val)
|
|
|
|
{
|
|
|
|
info->flags4 |= MF4_NOICEDEATH;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
info->flags4 &= ~MF4_NOICEDEATH;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else if (stricmp (Line1 + linelen - 6, " frame") == 0)
|
|
|
|
{
|
|
|
|
FState *state = FindState (val);
|
|
|
|
|
|
|
|
if (!strnicmp (Line1, "Initial", 7))
|
|
|
|
info->SpawnState = state ? state : GetDefault<AActor>()->SpawnState;
|
|
|
|
else if (!strnicmp (Line1, "First moving", 12))
|
|
|
|
info->SeeState = state;
|
|
|
|
else if (!strnicmp (Line1, "Injury", 6))
|
|
|
|
info->PainState = state;
|
|
|
|
else if (!strnicmp (Line1, "Close attack", 12))
|
|
|
|
info->MeleeState = state;
|
|
|
|
else if (!strnicmp (Line1, "Far attack", 10))
|
|
|
|
{
|
|
|
|
if (thingy != 1) // Not for players!
|
|
|
|
{
|
|
|
|
info->MissileState = state;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else if (!strnicmp (Line1, "Death", 5))
|
|
|
|
info->DeathState = state;
|
|
|
|
else if (!strnicmp (Line1, "Exploding", 9))
|
|
|
|
info->XDeathState = state;
|
|
|
|
else if (!strnicmp (Line1, "Respawn", 7))
|
|
|
|
info->RaiseState = state;
|
|
|
|
}
|
|
|
|
else if (stricmp (Line1 + linelen - 6, " sound") == 0)
|
|
|
|
{
|
|
|
|
int snd;
|
|
|
|
|
|
|
|
if (val == 0 || val >= (unsigned long)NumSounds)
|
|
|
|
{
|
|
|
|
if (endptr == Line2)
|
|
|
|
{ // Sound was not a (valid) number,
|
|
|
|
// so treat it as an actual sound name.
|
|
|
|
stripwhite (Line2);
|
|
|
|
snd = S_FindSound (Line2);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
snd = 0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
snd = S_FindSound (GetName (SoundMap[val-1]));
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!strnicmp (Line1, "Alert", 5))
|
|
|
|
info->SeeSound = snd;
|
|
|
|
else if (!strnicmp (Line1, "Attack", 6))
|
|
|
|
info->AttackSound = snd;
|
|
|
|
else if (!strnicmp (Line1, "Pain", 4))
|
|
|
|
info->PainSound = snd;
|
|
|
|
else if (!strnicmp (Line1, "Death", 5))
|
|
|
|
info->DeathSound = snd;
|
|
|
|
else if (!strnicmp (Line1, "Action", 6))
|
|
|
|
info->ActiveSound = snd;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else if (linelen == 4)
|
|
|
|
{
|
|
|
|
if (stricmp (Line1, "Mass") == 0)
|
|
|
|
{
|
|
|
|
info->Mass = val;
|
|
|
|
}
|
|
|
|
else if (stricmp (Line1, "Bits") == 0)
|
|
|
|
{
|
|
|
|
DWORD value[4] = { 0, 0, 0 };
|
|
|
|
bool vchanged[4] = { false, false, false };
|
|
|
|
char *strval;
|
|
|
|
|
|
|
|
for (strval = Line2; (strval = strtok (strval, ",+| \t\f\r")); strval = NULL)
|
|
|
|
{
|
|
|
|
if (IsNum (strval))
|
|
|
|
{
|
|
|
|
// Force the top 4 bits to 0 so that the user is forced
|
|
|
|
// to use the mnemonics to change them. And MF_SLIDE doesn't
|
|
|
|
// exist anymore, so 0 that too.
|
2006-04-11 16:27:41 +00:00
|
|
|
value[0] |= atoi(strval) & 0x0fffdfff;
|
2006-02-24 04:48:15 +00:00
|
|
|
vchanged[0] = true;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
int min, max;
|
|
|
|
int name = FindName (strval);
|
|
|
|
|
|
|
|
if (name == -1)
|
|
|
|
{
|
|
|
|
DPrintf ("Unknown bit mnemonic %s\n", strval);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
min = 0;
|
|
|
|
max = NumBitNames - 1;
|
|
|
|
while (min <= max)
|
|
|
|
{
|
|
|
|
int mid = (min + max) / 2;
|
|
|
|
if (BitNames[mid].Name == name)
|
|
|
|
{
|
|
|
|
vchanged[BitNames[mid].WhichFlags] = true;
|
|
|
|
value[BitNames[mid].WhichFlags] |= 1 << BitNames[mid].Bit;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
else if (BitNames[mid].Name < name)
|
|
|
|
{
|
|
|
|
min = mid + 1;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
max = mid - 1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (min > max)
|
|
|
|
{
|
|
|
|
DPrintf("Unknown bit mnemonic %s\n", strval);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (vchanged[0])
|
|
|
|
{
|
|
|
|
if (value[0] & MF_TRANSLATION)
|
|
|
|
{
|
|
|
|
info->Translation = TRANSLATION (TRANSLATION_Standard,
|
|
|
|
((value[0] & MF_TRANSLATION) >> (MF_TRANSSHIFT))-1);
|
|
|
|
value[0] &= ~MF_TRANSLATION;
|
|
|
|
}
|
|
|
|
info->flags = value[0];
|
|
|
|
}
|
|
|
|
if (vchanged[1])
|
|
|
|
{
|
|
|
|
info->flags2 = value[1];
|
|
|
|
if (info->flags2 & MF2_BOUNCE1)
|
|
|
|
{ // If a bex patch specifies FLOORBOUNCE, also set
|
|
|
|
// BOUNCE2, because otherwise it will get HERETICBOUNCE
|
|
|
|
// instead of DOOMBOUNCE.
|
|
|
|
info->flags2 |= MF2_BOUNCE2;
|
|
|
|
}
|
|
|
|
// Damage types that once were flags but now are not
|
|
|
|
if (info->flags2 & 0x20000000)
|
|
|
|
{
|
|
|
|
info->DamageType = MOD_ICE;
|
|
|
|
info->flags2 &= ~0x20000000;
|
|
|
|
}
|
|
|
|
if (info->flags2 & 0x10000)
|
|
|
|
{
|
|
|
|
info->DamageType = MOD_FIRE;
|
|
|
|
info->flags2 &= ~0x10000;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (vchanged[2])
|
|
|
|
{
|
|
|
|
if (value[2] & 7)
|
|
|
|
{
|
|
|
|
hadTranslucency = true;
|
|
|
|
if (value[2] & 1)
|
|
|
|
info->alpha = TRANSLUC25;
|
|
|
|
else if (value[2] & 2)
|
|
|
|
info->alpha = TRANSLUC50;
|
|
|
|
else if (value[2] & 4)
|
|
|
|
info->alpha = TRANSLUC75;
|
|
|
|
info->RenderStyle = STYLE_Translucent;
|
|
|
|
}
|
|
|
|
if (value[2] & 8)
|
|
|
|
info->renderflags |= RF_INVISIBLE;
|
|
|
|
else
|
|
|
|
info->renderflags &= ~RF_INVISIBLE;
|
|
|
|
}
|
|
|
|
DPrintf ("Bits: %ld,%ld (0x%08lx,0x%08lx)\n", info->flags, info->flags2,
|
|
|
|
info->flags, info->flags2);
|
|
|
|
}
|
|
|
|
else if (stricmp (Line1, "ID #") == 0)
|
|
|
|
{
|
|
|
|
*ednum = val;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else Printf (unknown_str, Line1, "Thing", thingy);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (info != &dummy)
|
|
|
|
{
|
|
|
|
// Reset heights for things hanging from the ceiling that
|
|
|
|
// don't specify a new height.
|
|
|
|
if (info->flags & MF_SPAWNCEILING &&
|
|
|
|
!hadHeight &&
|
|
|
|
thingy <= NumOrgHeights && thingy > 0)
|
|
|
|
{
|
|
|
|
info->height = OrgHeights[thingy - 1] * FRACUNIT;
|
|
|
|
}
|
|
|
|
// If the thing's shadow changed, change its fuzziness if not already specified
|
|
|
|
if ((info->flags ^ oldflags) & MF_SHADOW)
|
|
|
|
{
|
|
|
|
if (info->flags & MF_SHADOW)
|
|
|
|
{ // changed to shadow
|
|
|
|
if (!hadStyle)
|
|
|
|
info->RenderStyle = STYLE_OptFuzzy;
|
|
|
|
if (!hadTranslucency)
|
|
|
|
info->alpha = FRACUNIT/5;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{ // changed from shadow
|
|
|
|
if (!hadStyle)
|
|
|
|
info->RenderStyle = STYLE_Normal;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// If this thing's speed is really low (i.e. meant to be a monster),
|
|
|
|
// bump it up, because all speeds are fixed point now.
|
2006-06-14 03:57:58 +00:00
|
|
|
if (abs(info->Speed) < 256)
|
2006-02-24 04:48:15 +00:00
|
|
|
{
|
|
|
|
info->Speed <<= FRACBITS;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (info->flags & MF_SPECIAL)
|
|
|
|
{
|
2006-05-10 02:40:43 +00:00
|
|
|
TouchedActors.Push (const_cast<PClass *>(type));
|
2006-02-24 04:48:15 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Make MF3_ISMONSTER match MF_COUNTKILL
|
|
|
|
if (info->flags & MF_COUNTKILL)
|
|
|
|
{
|
|
|
|
info->flags3 |= MF3_ISMONSTER;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
info->flags3 &= ~MF3_ISMONSTER;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
|
|
// The only remotely useful thing Dehacked sound patches could do
|
|
|
|
// was change where the sound's name was stored. Since there is no
|
|
|
|
// real benefit to doing this, and it would be very difficult for
|
|
|
|
// me to emulate it, I have disabled them entirely.
|
|
|
|
|
|
|
|
static int PatchSound (int soundNum)
|
|
|
|
{
|
|
|
|
int result;
|
|
|
|
|
|
|
|
DPrintf ("Sound %d (no longer supported)\n", soundNum);
|
|
|
|
/*
|
|
|
|
sfxinfo_t *info, dummy;
|
|
|
|
int offset = 0;
|
|
|
|
if (soundNum >= 1 && soundNum <= NUMSFX) {
|
|
|
|
info = &S_sfx[soundNum];
|
|
|
|
} else {
|
|
|
|
info = &dummy;
|
|
|
|
Printf ("Sound %d out of range.\n");
|
|
|
|
}
|
|
|
|
*/
|
|
|
|
while ((result = GetLine ()) == 1) {
|
|
|
|
/*
|
|
|
|
if (!stricmp ("Offset", Line1))
|
|
|
|
offset = atoi (Line2);
|
|
|
|
else CHECKKEY ("Zero/One", info->singularity)
|
|
|
|
else CHECKKEY ("Value", info->priority)
|
|
|
|
else CHECKKEY ("Zero 1", info->link)
|
|
|
|
else CHECKKEY ("Neg. One 1", info->pitch)
|
|
|
|
else CHECKKEY ("Neg. One 2", info->volume)
|
|
|
|
else CHECKKEY ("Zero 2", info->data)
|
|
|
|
else CHECKKEY ("Zero 3", info->usefulness)
|
|
|
|
else CHECKKEY ("Zero 4", info->lumpnum)
|
|
|
|
else Printf (unknown_str, Line1, "Sound", soundNum);
|
|
|
|
*/
|
|
|
|
}
|
|
|
|
/*
|
|
|
|
if (offset) {
|
|
|
|
// Calculate offset from start of sound names
|
|
|
|
offset -= toff[dversion] + 21076;
|
|
|
|
|
|
|
|
if (offset <= 64) // pistol .. bfg
|
|
|
|
offset >>= 3;
|
|
|
|
else if (offset <= 260) // sawup .. oof
|
|
|
|
offset = (offset + 4) >> 3;
|
|
|
|
else // telept .. skeatk
|
|
|
|
offset = (offset + 8) >> 3;
|
|
|
|
|
|
|
|
if (offset >= 0 && offset < NUMSFX) {
|
|
|
|
S_sfx[soundNum].name = OrgSfxNames[offset + 1];
|
|
|
|
} else {
|
|
|
|
Printf ("Sound name %d out of range.\n", offset + 1);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
*/
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int PatchFrame (int frameNum)
|
|
|
|
{
|
|
|
|
int result;
|
|
|
|
int tics, misc1, frame;
|
|
|
|
FState *info, dummy;
|
|
|
|
|
|
|
|
info = FindState (frameNum);
|
|
|
|
if (info)
|
|
|
|
{
|
|
|
|
DPrintf ("Frame %d\n", frameNum);
|
|
|
|
if (frameNum == 47)
|
|
|
|
{ // Use original tics for S_DSGUNFLASH1
|
|
|
|
tics = 5;
|
|
|
|
}
|
|
|
|
else if (frameNum == 48)
|
|
|
|
{ // Ditto for S_DSGUNFLASH2
|
|
|
|
tics = 4;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
tics = info->GetTics ();
|
|
|
|
}
|
|
|
|
misc1 = info->GetMisc1 ();
|
|
|
|
frame = info->GetFrame () | (info->GetFullbright() ? 0x8000 : 0);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
info = &dummy;
|
|
|
|
tics = misc1 = frame = 0;
|
|
|
|
Printf ("Frame %d out of range\n", frameNum);
|
|
|
|
}
|
|
|
|
|
|
|
|
while ((result = GetLine ()) == 1)
|
|
|
|
{
|
|
|
|
int val = atoi (Line2);
|
|
|
|
size_t keylen = strlen (Line1);
|
|
|
|
|
|
|
|
if (keylen == 8 && stricmp (Line1, "Duration") == 0)
|
|
|
|
{
|
|
|
|
tics = clamp (val, -1, 65534);
|
|
|
|
}
|
|
|
|
else if (keylen == 9 && stricmp (Line1, "Unknown 1") == 0)
|
|
|
|
{
|
|
|
|
if (val < -128 || val > 127)
|
|
|
|
{
|
|
|
|
Printf ("Frame %d: misc1 is out of range\n", frameNum);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
misc1 = val;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else if (keylen == 9 && stricmp (Line1, "Unknown 2") == 0)
|
|
|
|
{
|
|
|
|
if (val < 0 || val > 255)
|
|
|
|
{
|
|
|
|
Printf ("Frame %d: misc2 is out of range\n", frameNum);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
info->Misc2 = val;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else if (keylen == 13 && stricmp (Line1, "Sprite number") == 0)
|
|
|
|
{
|
|
|
|
unsigned int i;
|
|
|
|
|
|
|
|
if (val < NumSprites)
|
|
|
|
{
|
|
|
|
for (i = 0; i < sprites.Size(); i++)
|
|
|
|
{
|
|
|
|
if (memcmp (OrgSprNames[val], sprites[i].name, 4) == 0)
|
|
|
|
{
|
|
|
|
info->sprite.index = (int)i;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (i == sprites.Size ())
|
|
|
|
{
|
|
|
|
Printf ("Frame %d: Sprite %d (%s) is undefined\n",
|
|
|
|
frameNum, val, OrgSprNames[val]);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
Printf ("Frame %d: Sprite %d out of range\n", frameNum, val);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else if (keylen == 10 && stricmp (Line1, "Next frame") == 0)
|
|
|
|
{
|
|
|
|
info->NextState = FindState (val);
|
|
|
|
}
|
|
|
|
else if (keylen == 16 && stricmp (Line1, "Sprite subnumber") == 0)
|
|
|
|
{
|
|
|
|
frame = val;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
Printf (unknown_str, Line1, "Frame", frameNum);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (info != &dummy)
|
|
|
|
{
|
|
|
|
if (misc1 != 0 && tics > 254)
|
|
|
|
{
|
|
|
|
Printf ("Frame %d: Misc1 must be 0 if tics >254\n", frameNum);
|
|
|
|
misc1 = 0;
|
|
|
|
}
|
|
|
|
if ((unsigned)(frame & 0x7fff) > 63)
|
|
|
|
{
|
|
|
|
Printf ("Frame %d: Subnumber must be in range [0,63]\n", frameNum);
|
|
|
|
}
|
|
|
|
info->Tics = (tics+1) & 255;
|
|
|
|
info->Misc1 = ((tics+1)>>8) | misc1;
|
|
|
|
info->Frame = (frame & 0x3f) |
|
|
|
|
(frame & 0x8000 ? SF_FULLBRIGHT : 0) |
|
|
|
|
(tics > 254 ? SF_BIGTIC : 0);
|
|
|
|
}
|
|
|
|
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int PatchSprite (int sprNum)
|
|
|
|
{
|
|
|
|
int result;
|
|
|
|
int offset = 0;
|
|
|
|
|
|
|
|
if (sprNum >= 0 && sprNum < NumSprites)
|
|
|
|
{
|
|
|
|
DPrintf ("Sprite %d\n", sprNum);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
Printf ("Sprite %d out of range.\n", sprNum);
|
|
|
|
sprNum = -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
while ((result = GetLine ()) == 1)
|
|
|
|
{
|
|
|
|
if (!stricmp ("Offset", Line1))
|
|
|
|
offset = atoi (Line2);
|
|
|
|
else Printf (unknown_str, Line1, "Sprite", sprNum);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (offset > 0 && sprNum != -1)
|
|
|
|
{
|
|
|
|
// Calculate offset from beginning of sprite names.
|
|
|
|
offset = (offset - toff[dversion] - 22044) / 8;
|
|
|
|
|
|
|
|
if (offset >= 0 && offset < NumSprites)
|
|
|
|
{
|
|
|
|
sprNum = FindSprite (OrgSprNames[sprNum]);
|
|
|
|
if (sprNum != -1)
|
|
|
|
strncpy (sprites[sprNum].name, OrgSprNames[offset], 4);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
Printf ("Sprite name %d out of range.\n", offset);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int PatchAmmo (int ammoNum)
|
|
|
|
{
|
2006-05-10 02:40:43 +00:00
|
|
|
const PClass *ammoType;
|
2006-02-24 04:48:15 +00:00
|
|
|
AAmmo *defaultAmmo;
|
|
|
|
int result;
|
|
|
|
int *max;
|
|
|
|
int *per;
|
|
|
|
int oldclip;
|
|
|
|
int dummy;
|
|
|
|
|
|
|
|
if (ammoNum >= 0 && ammoNum < 4)
|
|
|
|
{
|
|
|
|
DPrintf ("Ammo %d.\n", ammoNum);
|
2006-05-10 02:40:43 +00:00
|
|
|
ammoType = PClass::FindClass (AmmoNames[ammoNum]);
|
2006-02-24 04:48:15 +00:00
|
|
|
defaultAmmo = (AAmmo *)GetDefaultByType (ammoType);
|
|
|
|
max = &defaultAmmo->MaxAmount;
|
|
|
|
per = &defaultAmmo->Amount;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
Printf ("Ammo %d out of range.\n", ammoNum);
|
|
|
|
ammoType = NULL;
|
|
|
|
max = per = &dummy;
|
|
|
|
defaultAmmo = NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
oldclip = *per;
|
|
|
|
|
|
|
|
while ((result = GetLine ()) == 1)
|
|
|
|
{
|
|
|
|
CHECKKEY ("Max ammo", *max)
|
|
|
|
else CHECKKEY ("Per ammo", *per)
|
|
|
|
else Printf (unknown_str, Line1, "Ammo", ammoNum);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Calculate the new backpack-given amounts for this ammo.
|
|
|
|
if (ammoType != NULL)
|
|
|
|
{
|
|
|
|
defaultAmmo->BackpackMaxAmount = defaultAmmo->MaxAmount * 2;
|
|
|
|
defaultAmmo->BackpackAmount = defaultAmmo->Amount;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Fix per-ammo/max-ammo amounts for descendants of the base ammo class
|
|
|
|
if (oldclip != *per)
|
|
|
|
{
|
2006-05-16 04:19:20 +00:00
|
|
|
for (unsigned int i = 0; i < PClass::m_Types.Size(); ++i)
|
2006-02-24 04:48:15 +00:00
|
|
|
{
|
2006-05-10 02:40:43 +00:00
|
|
|
PClass *type = PClass::m_Types[i];
|
2006-02-24 04:48:15 +00:00
|
|
|
|
|
|
|
if (type == ammoType)
|
|
|
|
continue;
|
|
|
|
|
|
|
|
if (type->IsDescendantOf (ammoType))
|
|
|
|
{
|
|
|
|
defaultAmmo = (AAmmo *)GetDefaultByType (type);
|
|
|
|
defaultAmmo->MaxAmount = *max;
|
|
|
|
defaultAmmo->Amount = Scale (defaultAmmo->Amount, *per, oldclip);
|
|
|
|
}
|
|
|
|
else if (type->IsDescendantOf (RUNTIME_CLASS(AWeapon)))
|
|
|
|
{
|
|
|
|
AWeapon *defWeap = (AWeapon *)GetDefaultByType (type);
|
|
|
|
if (defWeap->AmmoType1 == ammoType)
|
|
|
|
{
|
|
|
|
defWeap->AmmoGive1 = Scale (defWeap->AmmoGive1, *per, oldclip);
|
|
|
|
}
|
|
|
|
if (defWeap->AmmoType2 == ammoType)
|
|
|
|
{
|
|
|
|
defWeap->AmmoGive2 = Scale (defWeap->AmmoGive2, *per, oldclip);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int PatchWeapon (int weapNum)
|
|
|
|
{
|
|
|
|
int result;
|
|
|
|
AWeapon *info;
|
|
|
|
BYTE dummy[sizeof(AWeapon)];
|
|
|
|
|
|
|
|
if (weapNum >= 0 && weapNum < 9)
|
|
|
|
{
|
|
|
|
info = (AWeapon *)GetDefaultByName (WeaponNames[weapNum]);
|
|
|
|
DPrintf ("Weapon %d\n", weapNum);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
info = (AWeapon *)&dummy;
|
|
|
|
Printf ("Weapon %d out of range.\n", weapNum);
|
|
|
|
}
|
|
|
|
|
|
|
|
while ((result = GetLine ()) == 1)
|
|
|
|
{
|
|
|
|
int val = atoi (Line2);
|
|
|
|
|
|
|
|
if (strlen (Line1) >= 9)
|
|
|
|
{
|
|
|
|
if (stricmp (Line1 + strlen (Line1) - 6, " frame") == 0)
|
|
|
|
{
|
|
|
|
FState *state = FindState (val);
|
|
|
|
|
|
|
|
if (strnicmp (Line1, "Deselect", 8) == 0)
|
|
|
|
info->UpState = state;
|
|
|
|
else if (strnicmp (Line1, "Select", 6) == 0)
|
|
|
|
info->DownState = state;
|
|
|
|
else if (strnicmp (Line1, "Bobbing", 7) == 0)
|
|
|
|
info->ReadyState = state;
|
|
|
|
else if (strnicmp (Line1, "Shooting", 8) == 0)
|
|
|
|
info->AtkState = info->HoldAtkState = state;
|
|
|
|
else if (strnicmp (Line1, "Firing", 6) == 0)
|
|
|
|
info->FlashState = state;
|
|
|
|
}
|
|
|
|
else if (stricmp (Line1, "Ammo type") == 0)
|
|
|
|
{
|
|
|
|
if (val < 0 || val >= 12)
|
|
|
|
{
|
|
|
|
val = 5;
|
|
|
|
}
|
2006-05-10 02:40:43 +00:00
|
|
|
info->AmmoType1 = PClass::FindClass (AmmoNames[val]);
|
2006-02-24 04:48:15 +00:00
|
|
|
if (info->AmmoType1 != NULL)
|
|
|
|
{
|
|
|
|
info->AmmoGive1 = ((AAmmo*)GetDefaultByType (info->AmmoType1))->Amount * 2;
|
|
|
|
if (info->AmmoUse1 == 0)
|
|
|
|
{
|
|
|
|
info->AmmoUse1 = 1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
Printf (unknown_str, Line1, "Weapon", weapNum);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else if (stricmp (Line1, "Decal") == 0)
|
|
|
|
{
|
|
|
|
stripwhite (Line2);
|
2006-04-12 01:50:09 +00:00
|
|
|
const FDecalTemplate *decal = DecalLibrary.GetDecalByName (Line2);
|
2006-02-24 04:48:15 +00:00
|
|
|
if (decal != NULL)
|
|
|
|
{
|
2006-04-12 01:50:09 +00:00
|
|
|
info->DecalGenerator = const_cast <FDecalTemplate *>(decal);
|
2006-02-24 04:48:15 +00:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
Printf ("Weapon %d: Unknown decal %s\n", weapNum, Line2);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else if (stricmp (Line1, "Ammo use") == 0 || stricmp (Line1, "Ammo per shot") == 0)
|
|
|
|
{
|
|
|
|
info->AmmoUse1 = val;
|
|
|
|
}
|
|
|
|
else if (stricmp (Line1, "Min ammo") == 0)
|
|
|
|
{
|
|
|
|
info->MinAmmo1 = val;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
Printf (unknown_str, Line1, "Weapon", weapNum);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (info->AmmoType1 == NULL)
|
|
|
|
{
|
|
|
|
info->AmmoUse1 = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int PatchPointer (int ptrNum)
|
|
|
|
{
|
|
|
|
int result;
|
|
|
|
|
|
|
|
if (ptrNum >= 0 && ptrNum < 448) {
|
|
|
|
DPrintf ("Pointer %d\n", ptrNum);
|
|
|
|
} else {
|
|
|
|
Printf ("Pointer %d out of range.\n", ptrNum);
|
|
|
|
ptrNum = -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
while ((result = GetLine ()) == 1)
|
|
|
|
{
|
|
|
|
if ((unsigned)ptrNum < (unsigned)NumCodeP && (!stricmp (Line1, "Codep Frame")))
|
|
|
|
{
|
|
|
|
FState *state = FindState (CodePConv[ptrNum]);
|
|
|
|
if (state)
|
|
|
|
{
|
|
|
|
if ((unsigned)(atoi (Line2)) >= (unsigned)NumActions)
|
|
|
|
state->Action = NULL;
|
|
|
|
else
|
|
|
|
state->Action = CodePtrs[ActionList[atoi (Line2)]];
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
Printf ("Bad code pointer %d\n", ptrNum);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else Printf (unknown_str, Line1, "Pointer", ptrNum);
|
|
|
|
}
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int PatchCheats (int dummy)
|
|
|
|
{
|
|
|
|
int result;
|
|
|
|
|
|
|
|
DPrintf ("Cheats (support removed by request)\n");
|
|
|
|
|
|
|
|
while ((result = GetLine ()) == 1)
|
|
|
|
{
|
|
|
|
}
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int PatchMisc (int dummy)
|
|
|
|
{
|
|
|
|
static const struct Key keys[] = {
|
|
|
|
{ "Initial Health", myoffsetof(struct DehInfo,StartHealth) },
|
|
|
|
{ "Initial Bullets", myoffsetof(struct DehInfo,StartBullets) },
|
|
|
|
{ "Max Health", myoffsetof(struct DehInfo,MaxHealth) },
|
|
|
|
{ "Max Armor", myoffsetof(struct DehInfo,MaxArmor) },
|
|
|
|
{ "Green Armor Class", myoffsetof(struct DehInfo,GreenAC) },
|
|
|
|
{ "Blue Armor Class", myoffsetof(struct DehInfo,BlueAC) },
|
|
|
|
{ "Max Soulsphere", myoffsetof(struct DehInfo,MaxSoulsphere) },
|
|
|
|
{ "Soulsphere Health", myoffsetof(struct DehInfo,SoulsphereHealth) },
|
|
|
|
{ "Megasphere Health", myoffsetof(struct DehInfo,MegasphereHealth) },
|
|
|
|
{ "God Mode Health", myoffsetof(struct DehInfo,GodHealth) },
|
|
|
|
{ "IDFA Armor", myoffsetof(struct DehInfo,FAArmor) },
|
|
|
|
{ "IDFA Armor Class", myoffsetof(struct DehInfo,FAAC) },
|
|
|
|
{ "IDKFA Armor", myoffsetof(struct DehInfo,KFAArmor) },
|
|
|
|
{ "IDKFA Armor Class", myoffsetof(struct DehInfo,KFAAC) },
|
|
|
|
{ "No Autofreeze", myoffsetof(struct DehInfo,NoAutofreeze) },
|
|
|
|
{ NULL, }
|
|
|
|
};
|
|
|
|
int result;
|
|
|
|
|
|
|
|
DPrintf ("Misc\n");
|
|
|
|
|
|
|
|
while ((result = GetLine()) == 1)
|
|
|
|
{
|
|
|
|
if (HandleKey (keys, &deh, Line1, atoi (Line2)))
|
|
|
|
{
|
|
|
|
if (stricmp (Line1, "BFG Cells/Shot") == 0)
|
|
|
|
{
|
|
|
|
((AWeapon*)GetDefaultByName ("BFG9000"))->AmmoUse1 = atoi (Line2);
|
|
|
|
}
|
|
|
|
else if (stricmp (Line1, "Rocket Explosion Style") == 0)
|
|
|
|
{
|
|
|
|
stripwhite (Line2);
|
|
|
|
int style = FindStyle (Line2);
|
|
|
|
if (style >= 0)
|
|
|
|
{
|
|
|
|
deh.ExplosionStyle = style;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else if (stricmp (Line1, "Rocket Explosion Alpha") == 0)
|
|
|
|
{
|
|
|
|
deh.ExplosionAlpha = (fixed_t)(atof (Line2) * FRACUNIT);
|
|
|
|
}
|
|
|
|
else if (stricmp (Line1, "Monsters Infight") == 0)
|
|
|
|
{
|
|
|
|
infighting = atoi (Line2);
|
|
|
|
}
|
|
|
|
else if (stricmp (Line1, "Monsters Ignore Each Other") == 0)
|
|
|
|
{
|
|
|
|
infighting = atoi (Line2) ? -1 : 0;
|
|
|
|
}
|
|
|
|
else if (strnicmp (Line1, "Powerup Color ", 14) == 0)
|
|
|
|
{
|
|
|
|
static const char * const names[] =
|
|
|
|
{
|
|
|
|
"Invulnerability",
|
|
|
|
"Berserk",
|
|
|
|
"Invisibility",
|
|
|
|
"Radiation Suit",
|
|
|
|
"Infrared",
|
|
|
|
"Tome of Power",
|
|
|
|
"Wings of Wrath",
|
|
|
|
"Speed",
|
|
|
|
"Minotaur",
|
|
|
|
NULL
|
|
|
|
};
|
2006-05-10 02:40:43 +00:00
|
|
|
static const PClass * const types[] =
|
2006-02-24 04:48:15 +00:00
|
|
|
{
|
|
|
|
RUNTIME_CLASS(APowerInvulnerable),
|
|
|
|
RUNTIME_CLASS(APowerStrength),
|
|
|
|
RUNTIME_CLASS(APowerInvisibility),
|
|
|
|
RUNTIME_CLASS(APowerIronFeet),
|
|
|
|
RUNTIME_CLASS(APowerLightAmp),
|
|
|
|
RUNTIME_CLASS(APowerWeaponLevel2),
|
|
|
|
RUNTIME_CLASS(APowerSpeed),
|
|
|
|
RUNTIME_CLASS(APowerMinotaur)
|
|
|
|
};
|
|
|
|
int i;
|
|
|
|
|
|
|
|
for (i = 0; names[i] != NULL; ++i)
|
|
|
|
{
|
|
|
|
if (stricmp (Line1 + 14, names[i]) == 0)
|
|
|
|
{
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (names[i] == NULL)
|
|
|
|
{
|
|
|
|
Printf ("Unknown miscellaneous info %s.\n", Line1);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
int r, g, b;
|
|
|
|
float a;
|
|
|
|
|
|
|
|
if (4 != sscanf (Line2, "%d %d %d %f", &r, &g, &b, &a))
|
|
|
|
{
|
|
|
|
Printf ("Bad powerup color description \"%s\" for %s\n", Line2, Line1);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
static_cast<APowerup *>(GetDefaultByType (types[i]))->BlendColor = PalEntry(
|
|
|
|
BYTE(clamp(a,0.f,1.f)*255.f),
|
|
|
|
clamp(r,0,255),
|
|
|
|
clamp(g,0,255),
|
|
|
|
clamp(b,0,255));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
Printf ("Unknown miscellaneous info %s.\n", Line1);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Update default item properties
|
|
|
|
ABasicArmorPickup *armor;
|
|
|
|
|
|
|
|
armor = static_cast<ABasicArmorPickup *> (GetDefaultByName ("GreenArmor"));
|
2006-05-03 14:54:48 +00:00
|
|
|
if (armor!=NULL)
|
|
|
|
{
|
|
|
|
armor->SaveAmount = 100 * deh.GreenAC;
|
|
|
|
armor->SavePercent = deh.GreenAC == 1 ? FRACUNIT/3 : FRACUNIT/2;
|
|
|
|
}
|
2006-02-24 04:48:15 +00:00
|
|
|
armor = static_cast<ABasicArmorPickup *> (GetDefaultByName ("BlueArmor"));
|
2006-05-03 14:54:48 +00:00
|
|
|
if (armor!=NULL)
|
|
|
|
{
|
|
|
|
armor->SaveAmount = 100 * deh.BlueAC;
|
|
|
|
armor->SavePercent = deh.BlueAC == 1 ? FRACUNIT/3 : FRACUNIT/2;
|
|
|
|
}
|
2006-02-24 04:48:15 +00:00
|
|
|
|
|
|
|
ABasicArmorBonus *barmor;
|
|
|
|
barmor = static_cast<ABasicArmorBonus *> (GetDefaultByName ("ArmorBonus"));
|
2006-05-03 14:54:48 +00:00
|
|
|
if (barmor!=NULL)
|
|
|
|
{
|
|
|
|
barmor->MaxSaveAmount = deh.MaxArmor;
|
|
|
|
}
|
2006-02-24 04:48:15 +00:00
|
|
|
|
|
|
|
AHealth *health;
|
|
|
|
health = static_cast<AHealth *> (GetDefaultByName ("HealthBonus"));
|
2006-05-03 14:54:48 +00:00
|
|
|
if (deh.MaxHealth != -1 && health!=NULL)
|
|
|
|
{
|
|
|
|
health->MaxAmount = deh.MaxHealth;
|
|
|
|
}
|
|
|
|
|
|
|
|
health = static_cast<AHealth *> (GetDefaultByName ("Soulsphere"));
|
|
|
|
if (health!=NULL)
|
|
|
|
{
|
|
|
|
health->Amount = deh.SoulsphereHealth;
|
|
|
|
health->MaxAmount = deh.MaxSoulsphere;
|
|
|
|
}
|
2006-02-24 04:48:15 +00:00
|
|
|
|
2006-07-13 10:17:56 +00:00
|
|
|
APlayerPawn *player = static_cast<APlayerPawn *> (GetDefaultByName ("DoomPlayer"));
|
|
|
|
if (player != NULL)
|
|
|
|
{
|
|
|
|
player->health = deh.StartHealth;
|
|
|
|
}
|
|
|
|
|
2006-02-24 04:48:15 +00:00
|
|
|
// 0xDD means "enable infighting"
|
|
|
|
if (infighting == 0xDD)
|
|
|
|
{
|
|
|
|
infighting = 1;
|
|
|
|
}
|
|
|
|
else if (infighting != -1)
|
|
|
|
{
|
|
|
|
infighting = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int PatchPars (int dummy)
|
|
|
|
{
|
|
|
|
char *space, mapname[8], *moredata;
|
|
|
|
level_info_t *info;
|
|
|
|
int result, par;
|
|
|
|
|
|
|
|
DPrintf ("[Pars]\n");
|
|
|
|
|
|
|
|
while ( (result = GetLine()) ) {
|
|
|
|
// Argh! .bex doesn't follow the same rules as .deh
|
|
|
|
if (result == 1) {
|
|
|
|
Printf ("Unknown key in [PARS] section: %s\n", Line1);
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
if (stricmp ("par", Line1))
|
|
|
|
return result;
|
|
|
|
|
|
|
|
space = strchr (Line2, ' ');
|
|
|
|
|
|
|
|
if (!space) {
|
|
|
|
Printf ("Need data after par.\n");
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
*space++ = '\0';
|
|
|
|
|
|
|
|
while (*space && isspace(*space))
|
|
|
|
space++;
|
|
|
|
|
|
|
|
moredata = strchr (space, ' ');
|
|
|
|
|
|
|
|
if (moredata) {
|
|
|
|
// At least 3 items on this line, must be E?M? format
|
|
|
|
sprintf (mapname, "E%cM%c", *Line2, *space);
|
|
|
|
par = atoi (moredata + 1);
|
|
|
|
} else {
|
|
|
|
// Only 2 items, must be MAP?? format
|
|
|
|
sprintf (mapname, "MAP%02d", atoi(Line2) % 100);
|
|
|
|
par = atoi (space);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!(info = FindLevelInfo (mapname)) ) {
|
|
|
|
Printf ("No map %s\n", mapname);
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
info->partime = par;
|
|
|
|
DPrintf ("Par for %s changed to %d\n", mapname, par);
|
|
|
|
}
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int PatchCodePtrs (int dummy)
|
|
|
|
{
|
|
|
|
int result;
|
|
|
|
|
|
|
|
DPrintf ("[CodePtr]\n");
|
|
|
|
|
|
|
|
while ((result = GetLine()) == 1)
|
|
|
|
{
|
|
|
|
if (!strnicmp ("Frame", Line1, 5) && isspace(Line1[5]))
|
|
|
|
{
|
|
|
|
int frame = atoi (Line1 + 5);
|
|
|
|
FState *state = FindState (frame);
|
|
|
|
|
|
|
|
if (state == NULL)
|
|
|
|
{
|
|
|
|
Printf ("Frame %d out of range\n", frame);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
int name;
|
|
|
|
|
|
|
|
stripwhite (Line2);
|
|
|
|
|
|
|
|
if ((Line2[0] == 'A' || Line2[0] == 'a') && Line2[1] == '_')
|
|
|
|
name = FindName (Line2 + 2);
|
|
|
|
else
|
|
|
|
name = FindName (Line2);
|
|
|
|
|
|
|
|
if (name == -1)
|
|
|
|
{
|
|
|
|
state->Action = NULL;
|
|
|
|
Printf ("Frame %d: Unknown code pointer: %s\n", frame, Line2);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
int min, max, mid;
|
|
|
|
|
|
|
|
min = 0;
|
|
|
|
max = NumCodePtrs - 1;
|
|
|
|
while (min <= max)
|
|
|
|
{
|
|
|
|
mid = (min + max) / 2;
|
|
|
|
if (CodePtrNames[mid].name == name)
|
|
|
|
break;
|
|
|
|
else if (CodePtrNames[mid].name < name)
|
|
|
|
min = mid + 1;
|
|
|
|
else
|
|
|
|
max = mid - 1;
|
|
|
|
}
|
|
|
|
if (min > max)
|
|
|
|
{
|
|
|
|
state->Action = NULL;
|
|
|
|
Printf ("Frame %d: Unknown code pointer: %s\n", frame, Line2);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
state->Action = CodePtrs[CodePtrNames[mid].num];
|
|
|
|
DPrintf ("Frame %d set to %s\n", frame, GetName (CodePtrNames[mid].name));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int PatchText (int oldSize)
|
|
|
|
{
|
|
|
|
int newSize;
|
|
|
|
char *oldStr;
|
|
|
|
char *newStr;
|
|
|
|
char *temp;
|
|
|
|
BOOL good;
|
|
|
|
int result;
|
|
|
|
int i;
|
|
|
|
|
|
|
|
// Skip old size, since we already know it
|
|
|
|
temp = Line2;
|
|
|
|
while (*temp > ' ')
|
|
|
|
temp++;
|
|
|
|
while (*temp && *temp <= ' ')
|
|
|
|
temp++;
|
|
|
|
|
|
|
|
if (*temp == 0)
|
|
|
|
{
|
|
|
|
Printf ("Text chunk is missing size of new string.\n");
|
|
|
|
return 2;
|
|
|
|
}
|
|
|
|
newSize = atoi (temp);
|
|
|
|
|
|
|
|
oldStr = new char[oldSize + 1];
|
|
|
|
newStr = new char[newSize + 1];
|
|
|
|
|
|
|
|
if (!oldStr || !newStr)
|
|
|
|
{
|
|
|
|
Printf ("Out of memory.\n");
|
|
|
|
goto donewithtext;
|
|
|
|
}
|
|
|
|
|
|
|
|
good = ReadChars (&oldStr, oldSize);
|
|
|
|
good += ReadChars (&newStr, newSize);
|
|
|
|
|
|
|
|
if (!good)
|
|
|
|
{
|
|
|
|
delete[] newStr;
|
|
|
|
delete[] oldStr;
|
|
|
|
Printf ("Unexpected end-of-file.\n");
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (includenotext)
|
|
|
|
{
|
|
|
|
Printf ("Skipping text chunk in included patch.\n");
|
|
|
|
goto donewithtext;
|
|
|
|
}
|
|
|
|
|
|
|
|
DPrintf ("Searching for text:\n%s\n", oldStr);
|
|
|
|
good = false;
|
|
|
|
|
|
|
|
// Search through sprite names; they are always 4 chars
|
|
|
|
if (oldSize == 4)
|
|
|
|
{
|
|
|
|
i = FindSprite (oldStr);
|
|
|
|
if (i != -1)
|
|
|
|
{
|
|
|
|
strncpy (sprites[i].name, newStr, 4);
|
|
|
|
if (strncmp ("PLAY", oldStr, 4) == 0)
|
|
|
|
{
|
|
|
|
strncpy (deh.PlayerSprite, newStr, 4);
|
|
|
|
}
|
|
|
|
// If this sprite is used by a pickup, then the DehackedPickup sprite map
|
|
|
|
// needs to be updated too.
|
- Fixed compilation with mingw again.
- Added multiple-choice sound sequences. These overcome one of the major
deficiences of the Hexen-inherited SNDSEQ system while still being Hexen
compatible: Custom door sounds can now use different opening and closing
sequences, for both normal and blazing speeds.
- Added a serializer for TArray.
- Added a countof macro to doomtype.h. See the1's blog to find out why
it's implemented the way it is.
<http://blogs.msdn.com/the1/articles/210011.aspx>
- Added a new method to FRandom for getting random numbers larger than 255,
which lets me:
- Fixed: SNDSEQ delayrand commands could delay for no more than 255 tics.
- Fixed: If you're going to have sector_t.SoundTarget, then they need to
be included in the pointer cleanup scans.
- Ported back newer name code from 2.1.
- Fixed: Using -warp with only one parameter in Doom and Heretic to
select a map on episode 1 no longer worked.
- New: Loading a multiplayer save now restores the players based on
their names rather than on their connection order. Using connection
order was sensible when -net was the only way to start a network game,
but with -host/-join, it's not so nice. Also, if there aren't enough
players in the save, then the extra players will be spawned normally,
so you can continue a saved game with more players than you started it
with.
- Added some new SNDSEQ commands to make it possible to define Heretic's
ambient sounds in SNDSEQ: volumerel, volumerand, slot, randomsequence,
delayonce, and restart. With these, it is basically possible to obsolete
all of the $ambient SNDINFO commands.
- Fixed: Sound sequences would only execute one command each time they were
ticked.
- Fixed: No bounds checking was done on the volume sound sequences played at.
- Fixed: The tic parameter to playloop was useless and caused it to
act like a redundant playrepeat. I have removed all the logic that
caused playloop to play repeating sounds, and now it acts like an
infinite sequence of play/delay commands until the sequence is
stopped.
- Fixed: Sound sequences were ticked every frame, not every tic, so all
the delay commands were timed incorrectly and varied depending on your
framerate. Since this is useful for restarting looping sounds that got
cut off, I have not changed this. Instead, the delay commands now
record the tic when execution should resume, not the number of tics
left to delay.
SVN r57 (trunk)
2006-04-21 01:22:55 +00:00
|
|
|
for (i = 0; (size_t)i < countof(DehSpriteMappings); ++i)
|
2006-02-24 04:48:15 +00:00
|
|
|
{
|
|
|
|
if (strncmp (DehSpriteMappings[i].Sprite, oldStr, 4) == 0)
|
|
|
|
{
|
|
|
|
// Found a match, so change it.
|
|
|
|
strncpy (DehSpriteMappings[i].Sprite, newStr, 4);
|
|
|
|
|
|
|
|
// Now shift the map's entries around so that it stays sorted.
|
|
|
|
// This must be done because the map is scanned using a binary search.
|
|
|
|
while (i > 0 && strncmp (DehSpriteMappings[i-1].Sprite, newStr, 4) > 0)
|
|
|
|
{
|
|
|
|
swap (DehSpriteMappings[i-1], DehSpriteMappings[i]);
|
|
|
|
--i;
|
|
|
|
}
|
- Fixed compilation with mingw again.
- Added multiple-choice sound sequences. These overcome one of the major
deficiences of the Hexen-inherited SNDSEQ system while still being Hexen
compatible: Custom door sounds can now use different opening and closing
sequences, for both normal and blazing speeds.
- Added a serializer for TArray.
- Added a countof macro to doomtype.h. See the1's blog to find out why
it's implemented the way it is.
<http://blogs.msdn.com/the1/articles/210011.aspx>
- Added a new method to FRandom for getting random numbers larger than 255,
which lets me:
- Fixed: SNDSEQ delayrand commands could delay for no more than 255 tics.
- Fixed: If you're going to have sector_t.SoundTarget, then they need to
be included in the pointer cleanup scans.
- Ported back newer name code from 2.1.
- Fixed: Using -warp with only one parameter in Doom and Heretic to
select a map on episode 1 no longer worked.
- New: Loading a multiplayer save now restores the players based on
their names rather than on their connection order. Using connection
order was sensible when -net was the only way to start a network game,
but with -host/-join, it's not so nice. Also, if there aren't enough
players in the save, then the extra players will be spawned normally,
so you can continue a saved game with more players than you started it
with.
- Added some new SNDSEQ commands to make it possible to define Heretic's
ambient sounds in SNDSEQ: volumerel, volumerand, slot, randomsequence,
delayonce, and restart. With these, it is basically possible to obsolete
all of the $ambient SNDINFO commands.
- Fixed: Sound sequences would only execute one command each time they were
ticked.
- Fixed: No bounds checking was done on the volume sound sequences played at.
- Fixed: The tic parameter to playloop was useless and caused it to
act like a redundant playrepeat. I have removed all the logic that
caused playloop to play repeating sounds, and now it acts like an
infinite sequence of play/delay commands until the sequence is
stopped.
- Fixed: Sound sequences were ticked every frame, not every tic, so all
the delay commands were timed incorrectly and varied depending on your
framerate. Since this is useful for restarting looping sounds that got
cut off, I have not changed this. Instead, the delay commands now
record the tic when execution should resume, not the number of tics
left to delay.
SVN r57 (trunk)
2006-04-21 01:22:55 +00:00
|
|
|
while ((size_t)i < countof(DehSpriteMappings)-1 &&
|
2006-02-24 04:48:15 +00:00
|
|
|
strncmp (DehSpriteMappings[i+1].Sprite, newStr, 4) < 0)
|
|
|
|
{
|
|
|
|
swap (DehSpriteMappings[i+1], DehSpriteMappings[i]);
|
|
|
|
++i;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
goto donewithtext;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#if 0
|
|
|
|
// Search through music names.
|
|
|
|
if (oldSize < 7)
|
|
|
|
{ // Music names are never >6 chars
|
|
|
|
char musname[9];
|
|
|
|
level_info_t *info = LevelInfos;
|
|
|
|
sprintf (musname, "d_%s", oldStr);
|
|
|
|
|
|
|
|
while (info->level_name)
|
|
|
|
{
|
|
|
|
if (info->music && stricmp (info->music, musname) == 0)
|
|
|
|
{
|
|
|
|
good = true;
|
|
|
|
strcpy (info->music, musname);
|
|
|
|
}
|
|
|
|
info++;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
|
|
|
if (good)
|
|
|
|
goto donewithtext;
|
|
|
|
|
|
|
|
// Search through most other texts
|
|
|
|
const char *str;
|
|
|
|
str = EnglishStrings->MatchString (oldStr);
|
|
|
|
if (str != NULL)
|
|
|
|
{
|
|
|
|
GStrings.SetString (str, newStr);
|
|
|
|
good = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!good)
|
|
|
|
DPrintf (" (Unmatched)\n");
|
|
|
|
|
|
|
|
donewithtext:
|
|
|
|
if (newStr)
|
|
|
|
delete[] newStr;
|
|
|
|
if (oldStr)
|
|
|
|
delete[] oldStr;
|
|
|
|
|
|
|
|
// Fetch next identifier for main loop
|
|
|
|
while ((result = GetLine ()) == 1)
|
|
|
|
;
|
|
|
|
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int PatchStrings (int dummy)
|
|
|
|
{
|
|
|
|
int result;
|
|
|
|
|
|
|
|
DPrintf ("[Strings]\n");
|
|
|
|
|
|
|
|
while ((result = GetLine()) == 1)
|
|
|
|
{
|
2006-06-11 01:06:19 +00:00
|
|
|
FString holdstring;
|
2006-02-24 04:48:15 +00:00
|
|
|
do
|
|
|
|
{
|
2006-06-11 01:06:19 +00:00
|
|
|
holdstring += skipwhite (Line2);
|
|
|
|
holdstring.StripRight();
|
|
|
|
if (holdstring.Len() > 0 && holdstring[holdstring.Len()-1] == '\\')
|
2006-02-24 04:48:15 +00:00
|
|
|
{
|
2006-06-11 01:06:19 +00:00
|
|
|
holdstring.Truncate((long)holdstring.Len()-1);
|
|
|
|
Line2 = igets ();
|
2006-02-24 04:48:15 +00:00
|
|
|
}
|
2006-06-11 01:06:19 +00:00
|
|
|
else
|
2006-02-24 04:48:15 +00:00
|
|
|
{
|
|
|
|
Line2 = NULL;
|
2006-06-11 01:06:19 +00:00
|
|
|
}
|
2006-02-24 04:48:15 +00:00
|
|
|
} while (Line2 && *Line2);
|
|
|
|
|
2006-06-11 01:06:19 +00:00
|
|
|
ReplaceSpecialChars (holdstring.LockBuffer());
|
|
|
|
holdstring.UnlockBuffer();
|
2006-02-24 04:48:15 +00:00
|
|
|
GStrings.SetString (Line1, holdstring);
|
2006-06-11 01:06:19 +00:00
|
|
|
DPrintf ("%s set to:\n%s\n", Line1, holdstring.GetChars());
|
2006-02-24 04:48:15 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int DoInclude (int dummy)
|
|
|
|
{
|
|
|
|
char *data;
|
|
|
|
int savedversion, savepversion;
|
|
|
|
char *savepatchfile, *savepatchpt, *savepatchname;
|
|
|
|
|
|
|
|
if (including)
|
|
|
|
{
|
|
|
|
Printf ("Sorry, can't nest includes\n");
|
|
|
|
return GetLine();
|
|
|
|
}
|
|
|
|
|
|
|
|
if (strnicmp (Line2, "notext", 6) == 0 && Line2[6] != 0 && isspace(Line2[6]))
|
|
|
|
{
|
|
|
|
includenotext = true;
|
|
|
|
Line2 = skipwhite (Line2+7);
|
|
|
|
}
|
|
|
|
|
|
|
|
stripwhite (Line2);
|
|
|
|
if (*Line2 == '\"')
|
|
|
|
{
|
|
|
|
data = ++Line2;
|
|
|
|
while (*data && *data != '\"')
|
|
|
|
data++;
|
|
|
|
*data = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (*Line2 == 0)
|
|
|
|
{
|
|
|
|
Printf ("Include directive is missing filename\n");
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
data = Line2;
|
|
|
|
DPrintf ("Including %s\n", data);
|
|
|
|
savepatchname = PatchName;
|
|
|
|
savepatchfile = PatchFile;
|
|
|
|
savepatchpt = PatchPt;
|
|
|
|
savedversion = dversion;
|
|
|
|
savepversion = pversion;
|
|
|
|
including = true;
|
|
|
|
|
|
|
|
// Try looking for the included file in the same directory
|
|
|
|
// as the patch before looking in the current file.
|
|
|
|
const char *lastSlash = savepatchname ? strrchr (savepatchname, '/') : NULL;
|
|
|
|
char *path = data;
|
|
|
|
|
|
|
|
if (lastSlash != NULL)
|
|
|
|
{
|
|
|
|
size_t pathlen = lastSlash - savepatchname + strlen (data) + 2;
|
|
|
|
path = new char[pathlen];
|
|
|
|
strncpy (path, savepatchname, (lastSlash - savepatchname) + 1);
|
|
|
|
strcpy (path + (lastSlash - savepatchname) + 1, data);
|
|
|
|
if (!FileExists (path))
|
|
|
|
{
|
|
|
|
delete[] path;
|
|
|
|
path = data;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
DoDehPatch (path, false);
|
|
|
|
|
|
|
|
if (data != path)
|
|
|
|
{
|
|
|
|
delete[] path;
|
|
|
|
}
|
|
|
|
|
|
|
|
DPrintf ("Done with include\n");
|
|
|
|
PatchName = savepatchname;
|
|
|
|
PatchFile = savepatchfile;
|
|
|
|
PatchPt = savepatchpt;
|
|
|
|
dversion = savedversion;
|
|
|
|
pversion = savepversion;
|
|
|
|
}
|
|
|
|
|
|
|
|
including = false;
|
|
|
|
includenotext = false;
|
|
|
|
return GetLine();
|
|
|
|
}
|
|
|
|
|
|
|
|
void DoDehPatch (const char *patchfile, BOOL autoloading)
|
|
|
|
{
|
|
|
|
char file[256];
|
|
|
|
int cont;
|
|
|
|
int filelen = 0; // Be quiet, gcc
|
|
|
|
int lump;
|
|
|
|
|
|
|
|
PatchFile = NULL;
|
|
|
|
PatchName = NULL;
|
|
|
|
|
|
|
|
lump = Wads.CheckNumForName ("DEHACKED");
|
|
|
|
|
|
|
|
if (lump >= 0 && autoloading)
|
|
|
|
{
|
|
|
|
// Execute the DEHACKED lump as a patch.
|
|
|
|
strcpy (file, "DEHACKED lump");
|
|
|
|
filelen = Wads.LumpLength (lump);
|
|
|
|
if ( (PatchFile = new char[filelen + 1]) )
|
|
|
|
{
|
|
|
|
Wads.ReadLump (lump, PatchFile);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
Printf ("Not enough memory to apply patch\n");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else if (patchfile)
|
|
|
|
{
|
|
|
|
// Try to use patchfile as a patch.
|
|
|
|
FILE *deh;
|
|
|
|
|
|
|
|
strcpy (file, patchfile);
|
|
|
|
FixPathSeperator (file);
|
|
|
|
DefaultExtension (file, ".deh");
|
|
|
|
|
|
|
|
if ( !(deh = fopen (file, "rb")) )
|
|
|
|
{
|
|
|
|
strcpy (file, patchfile);
|
|
|
|
FixPathSeperator (file);
|
|
|
|
DefaultExtension (file, ".bex");
|
|
|
|
deh = fopen (file, "rb");
|
|
|
|
}
|
|
|
|
|
|
|
|
if (deh)
|
|
|
|
{
|
|
|
|
filelen = Q_filelength (deh);
|
|
|
|
if ( (PatchFile = new char[filelen + 1]) )
|
|
|
|
{
|
|
|
|
fread (PatchFile, 1, filelen, deh);
|
|
|
|
fclose (deh);
|
|
|
|
PatchName = copystring (patchfile);
|
|
|
|
FixPathSeperator (PatchName);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!PatchFile)
|
|
|
|
{
|
|
|
|
// Couldn't find it on disk, try reading it from a lump
|
2006-07-08 02:17:35 +00:00
|
|
|
FString filebase(ExtractFileBase (patchfile));
|
|
|
|
lump = Wads.CheckNumForName (filebase);
|
2006-02-24 04:48:15 +00:00
|
|
|
if (lump >= 0)
|
|
|
|
{
|
|
|
|
filelen = Wads.LumpLength (lump);
|
|
|
|
if ( (PatchFile = new char[filelen + 1]) )
|
|
|
|
{
|
|
|
|
Wads.ReadLump (lump, PatchFile);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
Printf ("Not enough memory to apply patch\n");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!PatchFile)
|
|
|
|
{
|
|
|
|
Printf ("Could not open DeHackEd patch \"%s\"\n", file);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
// Nothing to do.
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// End file with a NULL for our parser
|
|
|
|
PatchFile[filelen] = 0;
|
|
|
|
|
|
|
|
dversion = pversion = -1;
|
|
|
|
/*
|
|
|
|
if (gameinfo.gametype != GAME_Doom)
|
|
|
|
{
|
|
|
|
Printf ("DeHackEd/BEX patches are only supported for DOOM mode\n");
|
|
|
|
delete[] PatchFile;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
*/
|
|
|
|
cont = 0;
|
|
|
|
if (0 == strncmp (PatchFile, "Patch File for DeHackEd v", 25))
|
|
|
|
{
|
|
|
|
if (PatchFile[25] < '3')
|
|
|
|
{
|
|
|
|
if (PatchName != NULL) delete[] PatchName;
|
|
|
|
delete[] PatchFile;
|
|
|
|
Printf (PRINT_BOLD, "\"%s\" is an old and unsupported DeHackEd patch\n", file);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
PatchPt = strchr (PatchFile, '\n');
|
|
|
|
while ((cont = GetLine()) == 1)
|
|
|
|
{
|
|
|
|
CHECKKEY ("Doom version", dversion)
|
|
|
|
else CHECKKEY ("Patch format", pversion)
|
|
|
|
}
|
|
|
|
if (!cont || dversion == -1 || pversion == -1)
|
|
|
|
{
|
|
|
|
if (PatchName != NULL) delete[] PatchName;
|
|
|
|
delete[] PatchFile;
|
|
|
|
Printf (PRINT_BOLD, "\"%s\" is not a DeHackEd patch file\n", file);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
DPrintf ("Patch does not have DeHackEd signature. Assuming .bex\n");
|
|
|
|
dversion = 19;
|
|
|
|
pversion = 6;
|
|
|
|
PatchPt = PatchFile;
|
|
|
|
while ((cont = GetLine()) == 1)
|
|
|
|
;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (pversion != 6)
|
|
|
|
{
|
|
|
|
Printf ("DeHackEd patch version is %d.\nUnexpected results may occur.\n", pversion);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (dversion == 16)
|
|
|
|
dversion = 0;
|
|
|
|
else if (dversion == 17)
|
|
|
|
dversion = 2;
|
|
|
|
else if (dversion == 19)
|
|
|
|
dversion = 3;
|
|
|
|
else if (dversion == 20)
|
|
|
|
dversion = 1;
|
|
|
|
else if (dversion == 21)
|
|
|
|
dversion = 4;
|
|
|
|
else
|
|
|
|
{
|
|
|
|
Printf ("Patch created with unknown DOOM version.\nAssuming version 1.9.\n");
|
|
|
|
dversion = 3;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!LoadDehSupp ())
|
|
|
|
{
|
|
|
|
Printf ("Could not load DEH support data\n");
|
|
|
|
if (PatchName != NULL) delete[] PatchName;
|
|
|
|
delete[] PatchFile;
|
|
|
|
UnloadDehSupp ();
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
do
|
|
|
|
{
|
|
|
|
if (cont == 1)
|
|
|
|
{
|
|
|
|
Printf ("Key %s encountered out of context\n", Line1);
|
|
|
|
cont = 0;
|
|
|
|
}
|
|
|
|
else if (cont == 2)
|
|
|
|
{
|
|
|
|
cont = HandleMode (Line1, atoi (Line2));
|
|
|
|
}
|
|
|
|
} while (cont);
|
|
|
|
|
|
|
|
UnloadDehSupp ();
|
|
|
|
if (PatchName != NULL) delete[] PatchName;
|
|
|
|
delete[] PatchFile;
|
|
|
|
Printf ("Patch installed\n");
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
static inline bool CompareLabel (const char *want, const BYTE *have)
|
|
|
|
{
|
|
|
|
return *(DWORD *)want == *(DWORD *)have;
|
|
|
|
}
|
|
|
|
|
|
|
|
static inline short GetWord (const BYTE *in)
|
|
|
|
{
|
|
|
|
return (in[0] << 8) | (in[1]);
|
|
|
|
}
|
|
|
|
|
|
|
|
static short *GetWordSpace (void *in, size_t size)
|
|
|
|
{
|
|
|
|
short *ptr;
|
|
|
|
size_t i;
|
|
|
|
|
|
|
|
ptr = (short *)in;
|
|
|
|
|
|
|
|
for (i = 0; i < size; i++)
|
|
|
|
{
|
|
|
|
ptr[i] = GetWord ((BYTE *)in + i*2);
|
|
|
|
}
|
|
|
|
return ptr;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int DehUseCount;
|
|
|
|
static BYTE *DehSuppLump;
|
|
|
|
|
|
|
|
static void UnloadDehSupp ()
|
|
|
|
{
|
|
|
|
if (--DehUseCount <= 0)
|
|
|
|
{
|
2006-06-11 01:06:19 +00:00
|
|
|
// StateMap is not freed here, because if you load a second
|
|
|
|
// dehacked patch through some means other than including it
|
|
|
|
// in the first patch, it won't see the state information
|
|
|
|
// that was altered by the first. So we need to keep the
|
|
|
|
// StateMap around until all patches have been applied.
|
2006-02-24 04:48:15 +00:00
|
|
|
DehUseCount = 0;
|
|
|
|
delete[] DehSuppLump;
|
|
|
|
DehSuppLump = NULL;
|
|
|
|
if (OrgSprNames != NULL)
|
|
|
|
{
|
|
|
|
delete[] OrgSprNames[0];
|
|
|
|
delete[] OrgSprNames;
|
|
|
|
OrgSprNames = NULL;
|
|
|
|
}
|
|
|
|
if (BitNames != NULL)
|
|
|
|
{
|
|
|
|
delete[] BitNames;
|
|
|
|
BitNames = NULL;
|
|
|
|
}
|
|
|
|
if (StyleNames != NULL)
|
|
|
|
{
|
|
|
|
delete[] StyleNames;
|
|
|
|
StyleNames = NULL;
|
|
|
|
}
|
|
|
|
if (UnchangedSpriteNames != NULL)
|
|
|
|
{
|
|
|
|
delete[] UnchangedSpriteNames;
|
|
|
|
UnchangedSpriteNames = NULL;
|
|
|
|
NumUnchangedSprites = 0;
|
|
|
|
}
|
|
|
|
if (EnglishStrings != NULL)
|
|
|
|
{
|
|
|
|
delete EnglishStrings;
|
2006-04-11 16:27:41 +00:00
|
|
|
EnglishStrings = NULL;
|
2006-02-24 04:48:15 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static bool LoadDehSupp ()
|
|
|
|
{
|
|
|
|
int lump = Wads.CheckNumForName ("DEHSUPP");
|
|
|
|
bool gotnames = false;
|
|
|
|
int i;
|
|
|
|
BYTE *supp;
|
|
|
|
|
|
|
|
if (lump == -1)
|
|
|
|
{
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (++DehUseCount > 1)
|
|
|
|
{
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (EnglishStrings == NULL)
|
|
|
|
{
|
|
|
|
EnglishStrings = new FStringTable;
|
|
|
|
EnglishStrings->LoadStrings (true);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (UnchangedSpriteNames == NULL)
|
|
|
|
{
|
|
|
|
UnchangedSpriteNames = new char[sprites.Size()*4];
|
|
|
|
NumUnchangedSprites = sprites.Size();
|
|
|
|
for (i = 0; i < NumUnchangedSprites; ++i)
|
|
|
|
{
|
|
|
|
memcpy (UnchangedSpriteNames+i*4, &sprites[i].name, 4);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (DehSuppLump != NULL)
|
|
|
|
{
|
|
|
|
supp = DehSuppLump;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
int len = Wads.LumpLength (lump);
|
|
|
|
supp = new BYTE[len];
|
|
|
|
Wads.ReadLump (lump, supp);
|
|
|
|
DehSuppLump = supp;
|
|
|
|
}
|
|
|
|
|
|
|
|
for (;;)
|
|
|
|
{
|
|
|
|
if (CompareLabel ("NAME", supp))
|
|
|
|
{
|
|
|
|
gotnames = true;
|
|
|
|
NumNames = GetWord (supp + 6);
|
|
|
|
NameBase = (char *)(supp + 8 + NumNames * 2);
|
|
|
|
NameOffs = (WORD *)GetWordSpace (supp + 8, NumNames);
|
|
|
|
supp += GetWord (supp + 4) + 6;
|
|
|
|
}
|
|
|
|
else if (CompareLabel ("HIGH", supp))
|
|
|
|
{
|
|
|
|
NumOrgHeights = GetWord (supp + 4);
|
|
|
|
OrgHeights = supp + 6;
|
|
|
|
supp += NumOrgHeights + 6;
|
|
|
|
}
|
|
|
|
else if (CompareLabel ("ACTF", supp))
|
|
|
|
{
|
|
|
|
NumCodePtrs = GetWord (supp + 4);
|
- Fixed compilation with mingw again.
- Added multiple-choice sound sequences. These overcome one of the major
deficiences of the Hexen-inherited SNDSEQ system while still being Hexen
compatible: Custom door sounds can now use different opening and closing
sequences, for both normal and blazing speeds.
- Added a serializer for TArray.
- Added a countof macro to doomtype.h. See the1's blog to find out why
it's implemented the way it is.
<http://blogs.msdn.com/the1/articles/210011.aspx>
- Added a new method to FRandom for getting random numbers larger than 255,
which lets me:
- Fixed: SNDSEQ delayrand commands could delay for no more than 255 tics.
- Fixed: If you're going to have sector_t.SoundTarget, then they need to
be included in the pointer cleanup scans.
- Ported back newer name code from 2.1.
- Fixed: Using -warp with only one parameter in Doom and Heretic to
select a map on episode 1 no longer worked.
- New: Loading a multiplayer save now restores the players based on
their names rather than on their connection order. Using connection
order was sensible when -net was the only way to start a network game,
but with -host/-join, it's not so nice. Also, if there aren't enough
players in the save, then the extra players will be spawned normally,
so you can continue a saved game with more players than you started it
with.
- Added some new SNDSEQ commands to make it possible to define Heretic's
ambient sounds in SNDSEQ: volumerel, volumerand, slot, randomsequence,
delayonce, and restart. With these, it is basically possible to obsolete
all of the $ambient SNDINFO commands.
- Fixed: Sound sequences would only execute one command each time they were
ticked.
- Fixed: No bounds checking was done on the volume sound sequences played at.
- Fixed: The tic parameter to playloop was useless and caused it to
act like a redundant playrepeat. I have removed all the logic that
caused playloop to play repeating sounds, and now it acts like an
infinite sequence of play/delay commands until the sequence is
stopped.
- Fixed: Sound sequences were ticked every frame, not every tic, so all
the delay commands were timed incorrectly and varied depending on your
framerate. Since this is useful for restarting looping sounds that got
cut off, I have not changed this. Instead, the delay commands now
record the tic when execution should resume, not the number of tics
left to delay.
SVN r57 (trunk)
2006-04-21 01:22:55 +00:00
|
|
|
if ((unsigned)NumCodePtrs != countof(CodePtrs))
|
2006-02-24 04:48:15 +00:00
|
|
|
{
|
|
|
|
Printf ("DEHSUPP defines %d code pointers, but there should be %d\n",
|
- Fixed compilation with mingw again.
- Added multiple-choice sound sequences. These overcome one of the major
deficiences of the Hexen-inherited SNDSEQ system while still being Hexen
compatible: Custom door sounds can now use different opening and closing
sequences, for both normal and blazing speeds.
- Added a serializer for TArray.
- Added a countof macro to doomtype.h. See the1's blog to find out why
it's implemented the way it is.
<http://blogs.msdn.com/the1/articles/210011.aspx>
- Added a new method to FRandom for getting random numbers larger than 255,
which lets me:
- Fixed: SNDSEQ delayrand commands could delay for no more than 255 tics.
- Fixed: If you're going to have sector_t.SoundTarget, then they need to
be included in the pointer cleanup scans.
- Ported back newer name code from 2.1.
- Fixed: Using -warp with only one parameter in Doom and Heretic to
select a map on episode 1 no longer worked.
- New: Loading a multiplayer save now restores the players based on
their names rather than on their connection order. Using connection
order was sensible when -net was the only way to start a network game,
but with -host/-join, it's not so nice. Also, if there aren't enough
players in the save, then the extra players will be spawned normally,
so you can continue a saved game with more players than you started it
with.
- Added some new SNDSEQ commands to make it possible to define Heretic's
ambient sounds in SNDSEQ: volumerel, volumerand, slot, randomsequence,
delayonce, and restart. With these, it is basically possible to obsolete
all of the $ambient SNDINFO commands.
- Fixed: Sound sequences would only execute one command each time they were
ticked.
- Fixed: No bounds checking was done on the volume sound sequences played at.
- Fixed: The tic parameter to playloop was useless and caused it to
act like a redundant playrepeat. I have removed all the logic that
caused playloop to play repeating sounds, and now it acts like an
infinite sequence of play/delay commands until the sequence is
stopped.
- Fixed: Sound sequences were ticked every frame, not every tic, so all
the delay commands were timed incorrectly and varied depending on your
framerate. Since this is useful for restarting looping sounds that got
cut off, I have not changed this. Instead, the delay commands now
record the tic when execution should resume, not the number of tics
left to delay.
SVN r57 (trunk)
2006-04-21 01:22:55 +00:00
|
|
|
NumCodePtrs, countof(CodePtrs));
|
2006-02-24 04:48:15 +00:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
CodePtrNames = (CodePtrMap *)GetWordSpace (supp + 6, NumCodePtrs*2);
|
|
|
|
supp += 6 + NumCodePtrs * 4;
|
|
|
|
}
|
|
|
|
else if (CompareLabel ("ACTM", supp))
|
|
|
|
{
|
|
|
|
NumActions = GetWord (supp + 4);
|
|
|
|
ActionList = supp + 6;
|
|
|
|
supp += NumActions + 6;
|
|
|
|
}
|
|
|
|
else if (CompareLabel ("CODP", supp))
|
|
|
|
{
|
|
|
|
NumCodeP = GetWord (supp + 4);
|
|
|
|
CodePConv = GetWordSpace (supp + 6, NumCodeP);
|
|
|
|
supp += 6 + NumCodeP * 2;
|
|
|
|
}
|
|
|
|
else if (CompareLabel ("SPRN", supp))
|
|
|
|
{
|
|
|
|
char *sprites;
|
|
|
|
|
|
|
|
NumSprites = GetWord (supp + 4);
|
|
|
|
OrgSprNames = new char *[NumSprites];
|
|
|
|
sprites = new char[NumSprites*5];
|
|
|
|
for (i = 0; i < NumSprites; i++)
|
|
|
|
{
|
|
|
|
sprites[i*5+0] = supp[6+i*4+0];
|
|
|
|
sprites[i*5+1] = supp[6+i*4+1];
|
|
|
|
sprites[i*5+2] = supp[6+i*4+2];
|
|
|
|
sprites[i*5+3] = supp[6+i*4+3];
|
|
|
|
sprites[i*5+4] = 0;
|
|
|
|
OrgSprNames[i] = sprites + i*5;
|
|
|
|
}
|
|
|
|
supp += 6 + NumSprites * 4;
|
|
|
|
}
|
|
|
|
else if (CompareLabel ("STAT", supp))
|
|
|
|
{
|
|
|
|
if (!gotnames)
|
|
|
|
{
|
|
|
|
Printf ("Names must come before state map\n");
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
if (StateMap == NULL)
|
|
|
|
{
|
|
|
|
NumStateMaps = GetWord (supp + 4);
|
|
|
|
StateMap = new StateMapper[NumStateMaps];
|
|
|
|
for (i = 0; i < NumStateMaps; i++)
|
|
|
|
{
|
|
|
|
const char *name = GetName (GetWord (supp + 6 + i*4));
|
2006-05-10 02:40:43 +00:00
|
|
|
const PClass *type = PClass::FindClass (name);
|
2006-02-24 04:48:15 +00:00
|
|
|
if (type == NULL)
|
|
|
|
{
|
|
|
|
Printf ("Can't find type %s\n", name);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
else if (type->ActorInfo == NULL)
|
|
|
|
{
|
|
|
|
Printf ("%s has no ActorInfo\n", name);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
AActor *def = GetDefaultByType (type);
|
|
|
|
|
|
|
|
switch (supp[6 + i*4 + 2])
|
|
|
|
{
|
|
|
|
case FirstState:
|
|
|
|
StateMap[i].State = type->ActorInfo->OwnedStates;
|
|
|
|
break;
|
|
|
|
case SpawnState:
|
|
|
|
StateMap[i].State = def->SpawnState;
|
|
|
|
break;
|
|
|
|
case DeathState:
|
|
|
|
StateMap[i].State = def->DeathState;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
StateMap[i].StateSpan = supp[6+i*4+3];
|
|
|
|
StateMap[i].Owner = type;
|
|
|
|
StateMap[i].OwnerIsPickup = (def->flags & MF_SPECIAL) != 0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
supp += 6 + NumStateMaps * 4;
|
|
|
|
}
|
|
|
|
else if (CompareLabel ("SND ", supp))
|
|
|
|
{
|
|
|
|
NumSounds = GetWord (supp + 4);
|
|
|
|
SoundMap = GetWordSpace (supp + 6, NumSounds);
|
|
|
|
supp += 6 + NumSounds * 2;
|
|
|
|
}
|
|
|
|
else if (CompareLabel ("INFN", supp))
|
|
|
|
{
|
|
|
|
NumInfos = GetWord (supp + 4);
|
|
|
|
InfoNames = GetWordSpace (supp + 6, NumInfos);
|
|
|
|
supp += 6 + NumInfos * 2;
|
|
|
|
}
|
|
|
|
else if (CompareLabel ("TBIT", supp))
|
|
|
|
{
|
|
|
|
NumBitNames = GetWord (supp + 4);
|
|
|
|
BitNames = new BitName[NumBitNames];
|
|
|
|
for (i = 0; i < NumBitNames; i++)
|
|
|
|
{
|
|
|
|
BitNames[i].Name = GetWord (supp + 6 + i*3);
|
|
|
|
BitNames[i].Bit = supp[6+i*3+2] & 0x1f;
|
|
|
|
BitNames[i].WhichFlags = clamp (supp[6+i*3+2] >> 5, 0, 3);
|
|
|
|
}
|
|
|
|
supp += 6 + NumBitNames * 3;
|
|
|
|
}
|
|
|
|
else if (CompareLabel ("REND", supp))
|
|
|
|
{
|
|
|
|
NumStyleNames = GetWord (supp + 4);
|
|
|
|
StyleNames = new StyleName[NumStyleNames];
|
|
|
|
for (i = 0; i < NumStyleNames; i++)
|
|
|
|
{
|
|
|
|
StyleNames[i].Name = GetWord (supp + 6 + i*3);
|
|
|
|
StyleNames[i].Num = supp[6+i*3+2];
|
|
|
|
}
|
|
|
|
supp += 6 + NumStyleNames * 3;
|
|
|
|
}
|
|
|
|
else if (CompareLabel ("END ", supp))
|
|
|
|
{
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
Printf ("Unknown block %c%c%c%c in DEHSUPP\n",
|
|
|
|
supp[0], supp[1], supp[2], supp[3]);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void FinishDehPatch ()
|
|
|
|
{
|
|
|
|
unsigned int touchedIndex;
|
|
|
|
|
|
|
|
for (touchedIndex = 0; touchedIndex < TouchedActors.Size(); ++touchedIndex)
|
|
|
|
{
|
2006-05-10 02:40:43 +00:00
|
|
|
PClass *type = TouchedActors[touchedIndex];
|
2006-02-24 04:48:15 +00:00
|
|
|
AActor *defaults1 = GetDefaultByType (type);
|
|
|
|
if (!(defaults1->flags & MF_SPECIAL))
|
|
|
|
{ // We only need to do this for pickups
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Create a new class that will serve as the actual pickup
|
|
|
|
char typeNameBuilder[32];
|
2006-05-10 02:40:43 +00:00
|
|
|
sprintf (typeNameBuilder, "DehackedPickup%d", touchedIndex);
|
|
|
|
PClass *subclass = RUNTIME_CLASS(ADehackedPickup)->CreateDerivedClass
|
2006-06-11 01:06:19 +00:00
|
|
|
(typeNameBuilder, sizeof(ADehackedPickup));
|
2006-02-24 04:48:15 +00:00
|
|
|
AActor *defaults2 = GetDefaultByType (subclass);
|
|
|
|
memcpy (defaults2, defaults1, sizeof(AActor));
|
|
|
|
subclass->ActorInfo->GameFilter = type->ActorInfo->GameFilter;
|
|
|
|
subclass->ActorInfo->SpawnID = type->ActorInfo->SpawnID;
|
|
|
|
subclass->ActorInfo->DoomEdNum = type->ActorInfo->DoomEdNum;
|
|
|
|
|
|
|
|
// Alter the original class so that it just spawns the new one
|
|
|
|
//memcpy (defaults1, GetDefault<AActor>(), sizeof(AActor));
|
|
|
|
defaults1->SpawnState = &ADehackedPickup::States[0];
|
|
|
|
defaults1->flags = 0;
|
|
|
|
defaults1->flags2 = 0;
|
|
|
|
defaults1->flags3 = 0;
|
|
|
|
defaults1->health = DehackedPickups.Push (subclass);
|
|
|
|
type->ActorInfo->SpawnID = 0;
|
|
|
|
type->ActorInfo->DoomEdNum = -1;
|
|
|
|
|
2006-05-10 02:40:43 +00:00
|
|
|
DPrintf ("%s replaces %s\n", subclass->TypeName.GetChars(), type->TypeName.GetChars());
|
2006-02-24 04:48:15 +00:00
|
|
|
}
|
2006-04-20 14:21:27 +00:00
|
|
|
|
|
|
|
// Since deh.MaxHealth was used incorrectly this can only be set
|
|
|
|
// after finishing with the DEH stuff.
|
|
|
|
if (deh.MaxHealth == -1) deh.MaxHealth = 100;
|
2006-06-11 01:06:19 +00:00
|
|
|
|
|
|
|
// Now that all Dehacked patches have been processed, it's okay to free StateMap.
|
|
|
|
if (StateMap != NULL)
|
|
|
|
{
|
|
|
|
delete[] StateMap;
|
|
|
|
StateMap = NULL;
|
|
|
|
}
|
2006-02-24 04:48:15 +00:00
|
|
|
}
|
|
|
|
|
2006-04-11 16:27:41 +00:00
|
|
|
void HandleNoSector()
|
|
|
|
{
|
|
|
|
// MF_NOSECTOR is causing problems with monsters so remap it to RF_INVISIBLE
|
|
|
|
// which in most cases is what this is used for anyway.
|
|
|
|
// Do this for all actors touched by DEHACKED actors except the teleport spot.
|
|
|
|
unsigned int touchedIndex;
|
|
|
|
|
|
|
|
for (touchedIndex = 0; touchedIndex < TouchedActors.Size(); ++touchedIndex)
|
|
|
|
{
|
2006-05-10 02:40:43 +00:00
|
|
|
PClass *ti = TouchedActors[touchedIndex];
|
2006-04-11 16:27:41 +00:00
|
|
|
|
|
|
|
if (ti!=NULL && ti->ActorInfo!=NULL && !ti->IsDescendantOf(RUNTIME_CLASS(ATeleportDest)))
|
|
|
|
{
|
|
|
|
AActor * def = GetDefaultByType(ti);
|
|
|
|
|
|
|
|
if (def->flags&MF_NOSECTOR)
|
|
|
|
{
|
|
|
|
def->flags&=~MF_NOSECTOR;
|
|
|
|
def->renderflags|=RF_INVISIBLE;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// The BossEye must be handled even without any Dehacked interference
|
|
|
|
// because otherwise it would not react to sound.
|
2006-05-10 02:40:43 +00:00
|
|
|
const PClass * ti = PClass::FindClass("BossEye");
|
2006-04-11 16:27:41 +00:00
|
|
|
if (ti!=NULL)
|
|
|
|
{
|
|
|
|
AActor * def = GetDefaultByType(ti);
|
|
|
|
|
|
|
|
if (def->flags&MF_NOSECTOR)
|
|
|
|
{
|
|
|
|
def->flags&=~MF_NOSECTOR;
|
|
|
|
def->renderflags|=RF_INVISIBLE;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2006-02-24 04:48:15 +00:00
|
|
|
void A_SpawnDehackedPickup (AActor *actor)
|
|
|
|
{
|
|
|
|
if ((size_t)actor->health < DehackedPickups.Size())
|
|
|
|
{
|
2006-07-16 09:10:45 +00:00
|
|
|
AActor *real = Spawn (DehackedPickups[actor->health], actor->x, actor->y, actor->z, NO_REPLACE);
|
2006-02-24 04:48:15 +00:00
|
|
|
if (real != NULL)
|
|
|
|
{
|
|
|
|
// Copy properties from the original item to the dehacked pickup it spawns
|
|
|
|
if (actor->flags & MF_DROPPED)
|
|
|
|
{
|
|
|
|
real->flags |= MF_DROPPED;
|
|
|
|
}
|
|
|
|
real->special = actor->special;
|
|
|
|
memcpy (real->args, actor->args, sizeof(real->args));
|
|
|
|
if (actor->tid != 0)
|
|
|
|
{
|
|
|
|
real->tid = actor->tid;
|
|
|
|
real->AddToHash ();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
bool ADehackedPickup::TryPickup (AActor *toucher)
|
|
|
|
{
|
2006-05-10 02:40:43 +00:00
|
|
|
const PClass *type = DetermineType ();
|
2006-02-24 04:48:15 +00:00
|
|
|
if (type == NULL)
|
|
|
|
{
|
|
|
|
return false;
|
|
|
|
}
|
2006-07-16 09:10:45 +00:00
|
|
|
RealPickup = static_cast<AInventory *>(Spawn (type, x, y, z, NO_REPLACE));
|
2006-02-24 04:48:15 +00:00
|
|
|
if (RealPickup != NULL)
|
|
|
|
{
|
|
|
|
if (!(flags & MF_DROPPED))
|
|
|
|
{
|
|
|
|
RealPickup->flags &= ~MF_DROPPED;
|
|
|
|
}
|
|
|
|
if (!RealPickup->TryPickup (toucher))
|
|
|
|
{
|
|
|
|
RealPickup->Destroy ();
|
|
|
|
RealPickup = NULL;
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
GoAwayAndDie ();
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
const char *ADehackedPickup::PickupMessage ()
|
|
|
|
{
|
|
|
|
return RealPickup->PickupMessage ();
|
|
|
|
}
|
|
|
|
|
|
|
|
bool ADehackedPickup::ShouldStay ()
|
|
|
|
{
|
|
|
|
return RealPickup->ShouldStay ();
|
|
|
|
}
|
|
|
|
|
|
|
|
bool ADehackedPickup::ShouldRespawn ()
|
|
|
|
{
|
|
|
|
return RealPickup->ShouldRespawn ();
|
|
|
|
}
|
|
|
|
|
|
|
|
void ADehackedPickup::PlayPickupSound (AActor *toucher)
|
|
|
|
{
|
|
|
|
RealPickup->PlayPickupSound (toucher);
|
|
|
|
}
|
|
|
|
|
|
|
|
void ADehackedPickup::DoPickupSpecial (AActor *toucher)
|
|
|
|
{
|
|
|
|
Super::DoPickupSpecial (toucher);
|
|
|
|
// If the real pickup hasn't joined the toucher's inventory, make sure it
|
|
|
|
// doesn't stick around.
|
|
|
|
if (RealPickup->Owner != toucher)
|
|
|
|
{
|
|
|
|
RealPickup->Destroy ();
|
|
|
|
}
|
|
|
|
RealPickup = NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
void ADehackedPickup::Destroy ()
|
|
|
|
{
|
|
|
|
if (RealPickup != NULL)
|
|
|
|
{
|
|
|
|
RealPickup->Destroy ();
|
|
|
|
RealPickup = NULL;
|
|
|
|
}
|
|
|
|
Super::Destroy ();
|
|
|
|
}
|
|
|
|
|
2006-05-10 02:40:43 +00:00
|
|
|
const PClass *ADehackedPickup::DetermineType ()
|
2006-02-24 04:48:15 +00:00
|
|
|
{
|
|
|
|
// Look at the actor's current sprite to determine what kind of
|
|
|
|
// item to pretend to me.
|
|
|
|
int min = 0;
|
- Fixed compilation with mingw again.
- Added multiple-choice sound sequences. These overcome one of the major
deficiences of the Hexen-inherited SNDSEQ system while still being Hexen
compatible: Custom door sounds can now use different opening and closing
sequences, for both normal and blazing speeds.
- Added a serializer for TArray.
- Added a countof macro to doomtype.h. See the1's blog to find out why
it's implemented the way it is.
<http://blogs.msdn.com/the1/articles/210011.aspx>
- Added a new method to FRandom for getting random numbers larger than 255,
which lets me:
- Fixed: SNDSEQ delayrand commands could delay for no more than 255 tics.
- Fixed: If you're going to have sector_t.SoundTarget, then they need to
be included in the pointer cleanup scans.
- Ported back newer name code from 2.1.
- Fixed: Using -warp with only one parameter in Doom and Heretic to
select a map on episode 1 no longer worked.
- New: Loading a multiplayer save now restores the players based on
their names rather than on their connection order. Using connection
order was sensible when -net was the only way to start a network game,
but with -host/-join, it's not so nice. Also, if there aren't enough
players in the save, then the extra players will be spawned normally,
so you can continue a saved game with more players than you started it
with.
- Added some new SNDSEQ commands to make it possible to define Heretic's
ambient sounds in SNDSEQ: volumerel, volumerand, slot, randomsequence,
delayonce, and restart. With these, it is basically possible to obsolete
all of the $ambient SNDINFO commands.
- Fixed: Sound sequences would only execute one command each time they were
ticked.
- Fixed: No bounds checking was done on the volume sound sequences played at.
- Fixed: The tic parameter to playloop was useless and caused it to
act like a redundant playrepeat. I have removed all the logic that
caused playloop to play repeating sounds, and now it acts like an
infinite sequence of play/delay commands until the sequence is
stopped.
- Fixed: Sound sequences were ticked every frame, not every tic, so all
the delay commands were timed incorrectly and varied depending on your
framerate. Since this is useful for restarting looping sounds that got
cut off, I have not changed this. Instead, the delay commands now
record the tic when execution should resume, not the number of tics
left to delay.
SVN r57 (trunk)
2006-04-21 01:22:55 +00:00
|
|
|
int max = countof(DehSpriteMappings) - 1;
|
2006-02-24 04:48:15 +00:00
|
|
|
|
|
|
|
while (min <= max)
|
|
|
|
{
|
|
|
|
int mid = (min + max) / 2;
|
|
|
|
int lex = memcmp (DehSpriteMappings[mid].Sprite, sprites[sprite].name, 4);
|
|
|
|
if (lex == 0)
|
|
|
|
{
|
2006-05-10 02:40:43 +00:00
|
|
|
return PClass::FindClass (DehSpriteMappings[mid].ClassName);
|
2006-02-24 04:48:15 +00:00
|
|
|
}
|
|
|
|
else if (lex < 0)
|
|
|
|
{
|
|
|
|
min = mid + 1;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
max = mid - 1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|