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"
|
2009-03-28 11:49:44 +00:00
|
|
|
#include "m_alloc.h"
|
2006-02-24 04:48:15 +00:00
|
|
|
#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 "v_palette.h"
|
2006-04-11 16:27:41 +00:00
|
|
|
#include "a_sharedglobal.h"
|
2007-05-28 14:46:49 +00:00
|
|
|
#include "thingdef/thingdef.h"
|
2009-09-14 22:12:31 +00:00
|
|
|
#include "thingdef/thingdef_exp.h"
|
2009-03-28 11:49:44 +00:00
|
|
|
#include "vectors.h"
|
|
|
|
#include "dobject.h"
|
2011-07-06 08:50:15 +00:00
|
|
|
#include "r_data/r_translate.h"
|
2009-03-28 11:49:44 +00:00
|
|
|
#include "sc_man.h"
|
2009-09-14 22:12:31 +00:00
|
|
|
#include "i_system.h"
|
2009-03-28 11:49:44 +00:00
|
|
|
#include "doomerrors.h"
|
2009-09-14 22:12:31 +00:00
|
|
|
#include "p_effect.h"
|
2011-07-06 14:20:54 +00:00
|
|
|
#include "farchive.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 ();
|
|
|
|
|
2009-03-28 11:49:44 +00:00
|
|
|
|
|
|
|
// This is a list of all the action functions used by each of Doom's states.
|
|
|
|
static TArray<PSymbol *> Actions;
|
|
|
|
|
|
|
|
// 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 TArray<int> OrgHeights;
|
|
|
|
|
|
|
|
// 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 TArray<int> CodePConv;
|
|
|
|
|
|
|
|
// Sprite names in the order Doom originally had them.
|
|
|
|
struct DEHSprName
|
|
|
|
{
|
|
|
|
char c[5];
|
|
|
|
};
|
|
|
|
static TArray<DEHSprName> OrgSprNames;
|
|
|
|
|
|
|
|
struct StateMapper
|
2006-02-24 04:48:15 +00:00
|
|
|
{
|
2009-03-28 11:49:44 +00:00
|
|
|
FState *State;
|
|
|
|
int StateSpan;
|
|
|
|
const PClass *Owner;
|
|
|
|
bool OwnerIsPickup;
|
2006-02-24 04:48:15 +00:00
|
|
|
};
|
|
|
|
|
2009-03-28 11:49:44 +00:00
|
|
|
static TArray<StateMapper> StateMap;
|
|
|
|
|
|
|
|
// Sound equivalences. When a patch tries to change a sound,
|
|
|
|
// use these sound names.
|
|
|
|
static TArray<FSoundID> SoundMap;
|
|
|
|
|
|
|
|
// Names of different actor types, in original Doom 2 order
|
|
|
|
static TArray<const PClass *> InfoNames;
|
2006-02-24 04:48:15 +00:00
|
|
|
|
2009-03-28 11:49:44 +00:00
|
|
|
// bit flags for PatchThing (a .bex extension):
|
|
|
|
struct BitName
|
2006-02-24 04:48:15 +00:00
|
|
|
{
|
2009-03-28 11:49:44 +00:00
|
|
|
char Name[20];
|
|
|
|
BYTE Bit;
|
|
|
|
BYTE WhichFlags;
|
2006-02-24 04:48:15 +00:00
|
|
|
};
|
|
|
|
|
2009-03-28 11:49:44 +00:00
|
|
|
static TArray<BitName> BitNames;
|
|
|
|
|
|
|
|
// Render styles
|
|
|
|
struct StyleName
|
2006-02-24 04:48:15 +00:00
|
|
|
{
|
2009-03-28 11:49:44 +00:00
|
|
|
char Name[20];
|
|
|
|
BYTE Num;
|
2006-02-24 04:48:15 +00:00
|
|
|
};
|
|
|
|
|
2009-03-28 11:49:44 +00:00
|
|
|
static TArray<StyleName> StyleNames;
|
|
|
|
|
|
|
|
static TArray<const PClass *> AmmoNames;
|
|
|
|
static TArray<const PClass *> WeaponNames;
|
|
|
|
|
2009-09-14 22:12:31 +00:00
|
|
|
// DeHackEd trickery to support MBF-style parameters
|
|
|
|
// List of states that are hacked to use a codepointer
|
|
|
|
struct MBFParamState
|
|
|
|
{
|
2009-09-15 22:44:07 +00:00
|
|
|
FState *state;
|
2009-09-14 22:12:31 +00:00
|
|
|
int pointer;
|
|
|
|
};
|
2009-09-15 23:31:28 +00:00
|
|
|
static TArray<MBFParamState> MBFParamStates;
|
2009-09-14 22:12:31 +00:00
|
|
|
// Data on how to correctly modify the codepointers
|
|
|
|
struct CodePointerAlias
|
|
|
|
{
|
|
|
|
char name[20];
|
|
|
|
char alias[20];
|
|
|
|
BYTE params;
|
|
|
|
};
|
|
|
|
static TArray<CodePointerAlias> MBFCodePointers;
|
|
|
|
|
2012-04-07 11:33:35 +00:00
|
|
|
struct AmmoPerAttack
|
|
|
|
{
|
|
|
|
actionf_p func;
|
|
|
|
int ammocount;
|
|
|
|
};
|
|
|
|
|
|
|
|
DECLARE_ACTION(A_Punch)
|
|
|
|
DECLARE_ACTION(A_FirePistol)
|
|
|
|
DECLARE_ACTION(A_FireShotgun)
|
|
|
|
DECLARE_ACTION(A_FireShotgun2)
|
|
|
|
DECLARE_ACTION(A_FireCGun)
|
|
|
|
DECLARE_ACTION(A_FireMissile)
|
|
|
|
DECLARE_ACTION_PARAMS(A_Saw)
|
|
|
|
DECLARE_ACTION(A_FirePlasma)
|
|
|
|
DECLARE_ACTION(A_FireBFG)
|
|
|
|
DECLARE_ACTION(A_FireOldBFG)
|
|
|
|
DECLARE_ACTION(A_FireRailgun)
|
|
|
|
|
|
|
|
// Default ammo use of the various weapon attacks
|
|
|
|
static AmmoPerAttack AmmoPerAttacks[] = {
|
|
|
|
{ AF_A_Punch, 0},
|
|
|
|
{ AF_A_FirePistol, 1},
|
|
|
|
{ AF_A_FireShotgun, 1},
|
|
|
|
{ AF_A_FireShotgun2, 2},
|
|
|
|
{ AF_A_FireCGun, 1},
|
|
|
|
{ AF_A_FireMissile, 1},
|
|
|
|
{ AFP_A_Saw, 0},
|
|
|
|
{ AF_A_FirePlasma, 1},
|
|
|
|
{ AF_A_FireBFG, -1}, // uses deh.BFGCells
|
|
|
|
{ AF_A_FireOldBFG, 1},
|
|
|
|
{ AF_A_FireRailgun, 1},
|
|
|
|
{ NULL, 0}
|
|
|
|
};
|
|
|
|
|
|
|
|
|
2006-02-24 04:48:15 +00:00
|
|
|
// Miscellaneous info that used to be constant
|
|
|
|
DehInfo deh =
|
|
|
|
{
|
|
|
|
100, // .StartHealth
|
|
|
|
50, // .StartBullets
|
2006-09-09 08:55:47 +00:00
|
|
|
100, // .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
|
2012-04-07 11:33:35 +00:00
|
|
|
40, // BFG cells per shot
|
2006-02-24 04:48:15 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
// 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.
|
|
|
|
|
|
|
|
IMPLEMENT_POINTY_CLASS (ADehackedPickup)
|
|
|
|
DECLARE_POINTER (RealPickup)
|
|
|
|
END_POINTERS
|
|
|
|
|
2006-05-10 02:40:43 +00:00
|
|
|
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;
|
2009-12-20 19:14:10 +00:00
|
|
|
static int PatchSize;
|
2006-02-24 04:48:15 +00:00
|
|
|
static char *Line1, *Line2;
|
|
|
|
static int dversion, pversion;
|
2006-09-14 00:02:31 +00:00
|
|
|
static bool including, includenotext;
|
2006-02-24 04:48:15 +00:00
|
|
|
|
|
|
|
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};
|
|
|
|
|
|
|
|
struct Key {
|
2006-12-29 03:38:37 +00:00
|
|
|
const char *name;
|
2006-02-24 04:48:15 +00:00
|
|
|
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);
|
2009-02-08 11:28:30 +00:00
|
|
|
static int PatchMusic (int);
|
2006-02-24 04:48:15 +00:00
|
|
|
static int DoInclude (int);
|
2009-11-12 02:47:28 +00:00
|
|
|
static bool DoDehPatch();
|
2006-02-24 04:48:15 +00:00
|
|
|
|
|
|
|
static const struct {
|
2006-12-29 03:38:37 +00:00
|
|
|
const char *name;
|
2006-02-24 04:48:15 +00:00
|
|
|
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 },
|
2009-02-08 11:28:30 +00:00
|
|
|
{ "[MUSIC]", PatchMusic },
|
2011-02-19 08:59:43 +00:00
|
|
|
{ NULL, NULL },
|
2006-02-24 04:48:15 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
static int HandleMode (const char *mode, int num);
|
2006-09-14 00:02:31 +00:00
|
|
|
static bool HandleKey (const struct Key *keys, void *structure, const char *key, int value);
|
|
|
|
static bool ReadChars (char **stuff, int size);
|
2006-02-24 04:48:15 +00:00
|
|
|
static char *igets (void);
|
|
|
|
static int GetLine (void);
|
|
|
|
|
2008-08-13 09:11:19 +00:00
|
|
|
static void PushTouchedActor(PClass *cls)
|
|
|
|
{
|
|
|
|
for(unsigned i = 0; i < TouchedActors.Size(); i++)
|
|
|
|
{
|
|
|
|
if (TouchedActors[i] == cls) return;
|
|
|
|
}
|
|
|
|
TouchedActors.Push(cls);
|
|
|
|
}
|
|
|
|
|
2006-02-24 04:48:15 +00:00
|
|
|
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2006-09-14 00:02:31 +00:00
|
|
|
static bool HandleKey (const struct Key *keys, void *structure, const char *key, int value)
|
2006-02-24 04:48:15 +00:00
|
|
|
{
|
|
|
|
while (keys->name && stricmp (keys->name, key))
|
|
|
|
keys++;
|
|
|
|
|
|
|
|
if (keys->name) {
|
2006-09-14 00:02:31 +00:00
|
|
|
*((int *)(((BYTE *)structure) + keys->offset)) = value;
|
2006-02-24 04:48:15 +00:00
|
|
|
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 stateacc;
|
2009-03-28 11:49:44 +00:00
|
|
|
unsigned i;
|
2006-02-24 04:48:15 +00:00
|
|
|
|
|
|
|
if (statenum == 0)
|
|
|
|
return NULL;
|
|
|
|
|
2009-03-28 11:49:44 +00:00
|
|
|
for (i = 0, stateacc = 1; i < StateMap.Size(); i++)
|
2006-02-24 04:48:15 +00:00
|
|
|
{
|
|
|
|
if (stateacc <= statenum && stateacc + StateMap[i].StateSpan > statenum)
|
|
|
|
{
|
2009-03-28 11:49:44 +00:00
|
|
|
if (StateMap[i].State != NULL)
|
2006-02-24 04:48:15 +00:00
|
|
|
{
|
2009-03-28 11:49:44 +00:00
|
|
|
if (StateMap[i].OwnerIsPickup)
|
|
|
|
{
|
|
|
|
PushTouchedActor(const_cast<PClass *>(StateMap[i].Owner));
|
|
|
|
}
|
|
|
|
return StateMap[i].State + statenum - stateacc;
|
2006-02-24 04:48:15 +00:00
|
|
|
}
|
2009-03-28 11:49:44 +00:00
|
|
|
else return NULL;
|
2006-02-24 04:48:15 +00:00
|
|
|
}
|
|
|
|
stateacc += StateMap[i].StateSpan;
|
|
|
|
}
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
int FindStyle (const char *namestr)
|
|
|
|
{
|
2009-03-28 11:49:44 +00:00
|
|
|
for(unsigned i = 0; i < StyleNames.Size(); i++)
|
2006-02-24 04:48:15 +00:00
|
|
|
{
|
2009-03-28 11:49:44 +00:00
|
|
|
if (!stricmp(StyleNames[i].Name, namestr)) return StyleNames[i].Num;
|
2006-02-24 04:48:15 +00:00
|
|
|
}
|
|
|
|
DPrintf("Unknown render style %s\n", namestr);
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
2006-09-14 00:02:31 +00:00
|
|
|
static bool ReadChars (char **stuff, int size)
|
2006-02-24 04:48:15 +00:00
|
|
|
{
|
|
|
|
char *str = *stuff;
|
|
|
|
|
|
|
|
if (!size) {
|
|
|
|
*str = 0;
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
do {
|
|
|
|
// Ignore carriage returns
|
|
|
|
if (*PatchPt != '\r')
|
|
|
|
*str++ = *PatchPt;
|
|
|
|
else
|
|
|
|
size++;
|
|
|
|
|
|
|
|
PatchPt++;
|
2009-12-20 19:14:10 +00:00
|
|
|
} while (--size && *PatchPt != 0);
|
2006-02-24 04:48:15 +00:00
|
|
|
|
|
|
|
*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;
|
|
|
|
|
2009-12-20 19:14:10 +00:00
|
|
|
if (*PatchPt == '\0' || PatchPt >= PatchFile + PatchSize )
|
2006-02-24 04:48:15 +00:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2009-09-14 22:12:31 +00:00
|
|
|
// This enum must be in sync with the Aliases array in DEHSUPP.
|
|
|
|
enum MBFCodePointers
|
|
|
|
{
|
|
|
|
// Die and Detonate are not in this list because these codepointers have
|
|
|
|
// no dehacked arguments and therefore do not need special handling.
|
|
|
|
// NailBomb has no argument but is implemented as new parameters for A_Explode.
|
|
|
|
MBF_Mushroom, // misc1 = vrange (arg +3), misc2 = hrange (arg+4)
|
|
|
|
MBF_Spawn, // misc1 = type (arg +0), misc2 = Z-pos (arg +2)
|
|
|
|
MBF_Turn, // misc1 = angle (in degrees) (arg +0 but factor in current actor angle too)
|
|
|
|
MBF_Face, // misc1 = angle (in degrees) (arg +0)
|
|
|
|
MBF_Scratch, // misc1 = damage, misc 2 = sound
|
|
|
|
MBF_PlaySound, // misc1 = sound, misc2 = attenuation none (true) or normal (false)
|
|
|
|
MBF_RandomJump, // misc1 = state, misc2 = probability
|
|
|
|
MBF_LineEffect, // misc1 = Boom linedef type, misc2 = sector tag
|
|
|
|
SMMU_NailBomb, // No misc, but it's basically A_Explode with an added effect
|
|
|
|
};
|
|
|
|
|
|
|
|
int PrepareStateParameters(FState * state, int numparams, const PClass *cls);// Should probably be in a .h file.
|
|
|
|
|
|
|
|
// Hacks the parameter list for the given state so as to convert MBF-args (misc1 and misc2) into real args.
|
|
|
|
|
|
|
|
void SetDehParams(FState * state, int codepointer)
|
|
|
|
{
|
|
|
|
int value1 = state->GetMisc1();
|
|
|
|
int value2 = state->GetMisc2();
|
|
|
|
if (!(value1|value2)) return;
|
|
|
|
|
|
|
|
// Fakey fake script position thingamajig. Because NULL cannot be used instead.
|
|
|
|
// Even if the lump was parsed by an FScanner, there would hardly be a way to
|
|
|
|
// identify which line is troublesome.
|
|
|
|
FScriptPosition * pos = new FScriptPosition(FString("DEHACKED"), 0);
|
|
|
|
|
|
|
|
// Let's identify the codepointer we're dealing with.
|
|
|
|
PSymbolActionFunction * sym; PSymbol * s;
|
|
|
|
s = RUNTIME_CLASS(AInventory)->Symbols.FindSymbol(FName(MBFCodePointers[codepointer].name), true);
|
|
|
|
if (!s || s->SymbolType != SYM_ActionFunction) return;
|
|
|
|
sym = static_cast<PSymbolActionFunction*>(s);
|
|
|
|
|
|
|
|
|
|
|
|
// Bleargh! This will all have to be redone once scripting works
|
|
|
|
|
|
|
|
// Not sure exactly why the index for a state is greater by one point than the index for a symbol.
|
|
|
|
DPrintf("SetDehParams: Paramindex is %d, default is %d.\n",
|
|
|
|
state->ParameterIndex-1, sym->defaultparameterindex);
|
|
|
|
if (state->ParameterIndex-1 == sym->defaultparameterindex)
|
|
|
|
{
|
|
|
|
int a = PrepareStateParameters(state, MBFCodePointers[codepointer].params+1,
|
|
|
|
FState::StaticFindStateOwner(state)) -1;
|
|
|
|
int b = sym->defaultparameterindex;
|
|
|
|
// StateParams.Copy(a, b, MBFParams[codepointer]);
|
|
|
|
// Meh, function doesn't work. For some reason it resets the paramindex to the default value.
|
|
|
|
// For instance, a dehacked Commander Keen calling A_Explode would result in a crash as
|
|
|
|
// ACTION_PARAM_INT(damage, 0) would properly evaluate at paramindex 1377, but then
|
|
|
|
// ACTION_PARAM_INT(distance, 1) would improperly evaluate at paramindex 148! Now I'm not sure
|
|
|
|
// whether it's a genuine problem or working as intended and merely not appropriate for the
|
|
|
|
// task at hand here. So rather than modify it, I use a simple for loop of Set()s and Get()s,
|
|
|
|
// with a small modification to Set() that I know will have no repercussion anywhere else.
|
|
|
|
for (int i = 0; i<MBFCodePointers[codepointer].params; i++)
|
|
|
|
{
|
|
|
|
StateParams.Set(a+i, StateParams.Get(b+i), true);
|
|
|
|
}
|
|
|
|
DPrintf("New paramindex is %d.\n", state->ParameterIndex-1);
|
|
|
|
}
|
|
|
|
int ParamIndex = state->ParameterIndex - 1;
|
|
|
|
|
|
|
|
switch (codepointer)
|
|
|
|
{
|
|
|
|
case MBF_Mushroom:
|
|
|
|
StateParams.Set(ParamIndex+2, new FxConstant(1, *pos)); // Flag
|
|
|
|
// NOTE: Do not convert to float here because it will lose precision. It must be double.
|
|
|
|
if (value1) StateParams.Set(ParamIndex+3, new FxConstant(value1/65536., *pos)); // vrange
|
|
|
|
if (value2) StateParams.Set(ParamIndex+4, new FxConstant(value2/65536., *pos)); // hrange
|
|
|
|
break;
|
|
|
|
case MBF_Spawn:
|
|
|
|
if (InfoNames[value1-1] == NULL)
|
|
|
|
{
|
|
|
|
I_Error("No class found for dehackednum %d!\n", value1+1);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
StateParams.Set(ParamIndex+0, new FxConstant(InfoNames[value1-1], *pos)); // type
|
|
|
|
StateParams.Set(ParamIndex+2, new FxConstant(value2, *pos)); // height
|
|
|
|
break;
|
|
|
|
case MBF_Turn:
|
|
|
|
// Intentional fall through. I tried something more complicated by creating an
|
|
|
|
// FxExpression that corresponded to "variable angle + angle" so as to use A_SetAngle
|
|
|
|
// as well, but it became an overcomplicated mess that didn't even work as I had to
|
|
|
|
// create a compile context as well and couldn't get it right.
|
|
|
|
case MBF_Face:
|
|
|
|
StateParams.Set(ParamIndex+0, new FxConstant(value1, *pos)); // angle
|
|
|
|
break;
|
|
|
|
case MBF_Scratch: // misc1 = damage, misc 2 = sound
|
|
|
|
StateParams.Set(ParamIndex+0, new FxConstant(value1, *pos)); // damage
|
|
|
|
if (value2) StateParams.Set(ParamIndex+1, new FxConstant(SoundMap[value2-1], *pos)); // hit sound
|
|
|
|
break;
|
|
|
|
case MBF_PlaySound:
|
2011-11-24 03:59:02 +00:00
|
|
|
StateParams.Set(ParamIndex+0, new FxConstant(SoundMap[value1-1], *pos)); // soundid
|
|
|
|
StateParams.Set(ParamIndex+1, new FxConstant(CHAN_BODY, *pos)); // channel
|
|
|
|
StateParams.Set(ParamIndex+2, new FxConstant(1.0, *pos)); // volume
|
|
|
|
StateParams.Set(ParamIndex+3, new FxConstant(false, *pos)); // looping
|
|
|
|
StateParams.Set(ParamIndex+4, new FxConstant((value2 ? ATTN_NONE : ATTN_NORM), *pos)); // attenuation
|
2009-09-14 22:12:31 +00:00
|
|
|
break;
|
|
|
|
case MBF_RandomJump:
|
|
|
|
StateParams.Set(ParamIndex+0, new FxConstant(2, *pos)); // count
|
|
|
|
StateParams.Set(ParamIndex+1, new FxConstant(value2, *pos)); // maxchance
|
|
|
|
StateParams.Set(ParamIndex+2, new FxConstant(FindState(value1), *pos)); // jumpto
|
|
|
|
break;
|
|
|
|
case MBF_LineEffect:
|
|
|
|
// This is the second MBF codepointer that couldn't be translated easily.
|
|
|
|
// Calling P_TranslateLineDef() here was a simple matter, as was adding an
|
|
|
|
// extra parameter to A_CallSpecial so as to replicate the LINEDONE stuff,
|
|
|
|
// but unfortunately DEHACKED lumps are processed before the map translation
|
|
|
|
// arrays are initialized so this didn't work.
|
|
|
|
StateParams.Set(ParamIndex+0, new FxConstant(value1, *pos)); // special
|
|
|
|
StateParams.Set(ParamIndex+1, new FxConstant(value2, *pos)); // tag
|
|
|
|
break;
|
|
|
|
case SMMU_NailBomb:
|
|
|
|
// That one does not actually have MBF-style parameters. But since
|
|
|
|
// we're aliasing it to an extension of A_Explode...
|
|
|
|
StateParams.Set(ParamIndex+5, new FxConstant(30, *pos)); // nails
|
|
|
|
StateParams.Set(ParamIndex+6, new FxConstant(10, *pos)); // naildamage
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
// This simply should not happen.
|
|
|
|
Printf("Unmanaged dehacked codepointer alias num %i\n", codepointer);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2006-02-24 04:48:15 +00:00
|
|
|
static int PatchThing (int thingy)
|
|
|
|
{
|
|
|
|
enum
|
|
|
|
{
|
2009-09-14 22:12:31 +00:00
|
|
|
// Boom flags
|
2006-02-24 04:48:15 +00:00
|
|
|
MF_TRANSLATION = 0x0c000000, // if 0x4 0x8 or 0xc, use a translation
|
|
|
|
MF_TRANSSHIFT = 26, // table for player colormaps
|
2009-09-14 22:12:31 +00:00
|
|
|
// A couple of Boom flags that don't exist in ZDoom
|
|
|
|
MF_SLIDE = 0x00002000, // Player: keep info about sliding along walls.
|
|
|
|
MF_TRANSLUCENT = 0x80000000, // Translucent sprite?
|
|
|
|
// MBF flags: TOUCHY is remapped to flags6, FRIEND is turned into FRIENDLY,
|
|
|
|
// and finally BOUNCES is replaced by bouncetypes with the BOUNCES_MBF bit.
|
|
|
|
MF_TOUCHY = 0x10000000, // killough 11/98: dies when solids touch it
|
|
|
|
MF_BOUNCES = 0x20000000, // killough 7/11/98: for beta BFG fireballs
|
|
|
|
MF_FRIEND = 0x40000000, // killough 7/18/98: friendly monsters
|
2006-02-24 04:48:15 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
int result;
|
2008-03-13 00:45:35 +00:00
|
|
|
AActor *info;
|
|
|
|
BYTE dummy[sizeof(AActor)];
|
2006-02-24 04:48:15 +00:00
|
|
|
bool hadHeight = false;
|
|
|
|
bool hadTranslucency = false;
|
|
|
|
bool hadStyle = false;
|
2008-09-22 18:55:29 +00:00
|
|
|
FStateDefinitions statedef;
|
2006-10-31 14:53:21 +00:00
|
|
|
bool patchedStates = false;
|
2006-02-24 04:48:15 +00:00
|
|
|
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;
|
2008-03-13 00:45:35 +00:00
|
|
|
info = (AActor *)&dummy;
|
2006-02-24 04:48:15 +00:00
|
|
|
ednum = &dummyed;
|
2009-03-28 11:49:44 +00:00
|
|
|
if (thingy > (int)InfoNames.Size() || thingy <= 0)
|
2006-02-24 04:48:15 +00:00
|
|
|
{
|
|
|
|
Printf ("Thing %d out of range.\n", thingy);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
DPrintf ("Thing %d\n", thingy);
|
|
|
|
if (thingy > 0)
|
|
|
|
{
|
2009-03-28 11:49:44 +00:00
|
|
|
type = InfoNames[thingy - 1];
|
2006-02-24 04:48:15 +00:00
|
|
|
if (type == NULL)
|
|
|
|
{
|
2008-03-13 00:45:35 +00:00
|
|
|
info = (AActor *)&dummy;
|
2006-02-24 04:48:15 +00:00
|
|
|
ednum = &dummyed;
|
2009-03-28 11:49:44 +00:00
|
|
|
// An error for the name has already been printed while loading DEHSUPP.
|
|
|
|
Printf ("Could not find thing %d\n", thingy);
|
2006-02-24 04:48:15 +00:00
|
|
|
}
|
|
|
|
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)
|
|
|
|
{
|
2009-05-15 10:39:40 +00:00
|
|
|
info->PainChance = (SWORD)val;
|
2006-02-24 04:48:15 +00:00
|
|
|
}
|
|
|
|
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;
|
2008-10-05 11:23:41 +00:00
|
|
|
info->projectilepassheight = 0; // needs to be disabled
|
2006-02-24 04:48:15 +00:00
|
|
|
hadHeight = true;
|
|
|
|
}
|
|
|
|
else if (linelen == 14 && stricmp (Line1, "Missile damage") == 0)
|
|
|
|
{
|
- Added the ACS commands
ReplaceTextures (str old_texture, str new_texture, optional bool not_lower,
optional bool not_mid, optional bool not_upper, optional bool not_floor,
optional bool not_ceiling); and
SectorDamage (int tag, int amount, str type, bool players_only, bool in_air,
str protection_item, bool subclasses_okay);
- Added the vid_nowidescreen cvar to disable widescreen aspect ratio
correction. When this is enabled, the only display ratio available is 4:3
(and 5:4 if vid_tft is set).
- Added support for setting an actor's damage property to an expression
through decorate. Just enclose it within parentheses, and the expression
will be evaluated exactly as-is without the normal Doom damage calculation.
So if you want something that does exactly 6 damage, use a "Damage (6)"
property. To deal normal Doom missile damage, you can use
"Damage (random(1,8)*6)" instead of "Damage 6".
- Moved InvFirst and InvSel into APlayerPawn so that they can be consistantly
maintained by ObtainInventory.
SVN r288 (trunk)
2006-08-12 02:30:57 +00:00
|
|
|
info->Damage = val;
|
2006-02-24 04:48:15 +00:00
|
|
|
}
|
|
|
|
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)
|
|
|
|
{
|
2006-11-14 16:54:02 +00:00
|
|
|
info->scaleY = info->scaleX = clamp<fixed_t> (FLOAT2FIXED(atof (Line2)), 1, 256*FRACUNIT);
|
2006-02-24 04:48:15 +00:00
|
|
|
}
|
|
|
|
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)
|
|
|
|
{
|
- Updated lempar.c to v1.31.
- Added .txt files to the list of types (wad, zip, and pk3) that can be
loaded without listing them after -file.
- Fonts that are created by the ACS setfont command to wrap a texture now
support animated textures.
- FON2 fonts can now use their full palette for CR_UNTRANSLATED when drawn
with the hardware 2D path instead of being restricted to the game palette.
- Fixed: Toggling vid_vsync would reset the displayed fullscreen gamma to 1
on a Radeon 9000.
- Added back the off-by-one palette handling, but in a much more limited
scope than before. The skipped entry is assumed to always be at 248, and
it is assumed that all Shader Model 1.4 cards suffer from this. That's
because all SM1.4 cards are based on variants of the ATI R200 core, and the
RV250 in a Radeon 9000 craps up like this. I see no reason to assume that
other flavors of the R200 are any different. (Interesting note: With the
Radeon 9000, D3DTADDRESS_CLAMP is an invalid address mode when using the
debug Direct3D 9 runtime, but it works perfectly fine with the retail
Direct3D 9 runtime.) (Insight: The R200 probably uses bytes for all its
math inside pixel shaders. That would explain perfectly why I can't use
constants greater than 1 with PS1.4 and why it can't do an exact mapping to
every entry in the color palette.
- Fixed: The software shaded drawer did not work for 2D, because its selected
"color"map was replaced with the identitymap before being used.
- Fixed: I cannot use Printf to output messages before the framebuffer was
completely setup, meaning that Shader Model 1.4 cards could not change
resolution.
- I have decided to let remap palettes specify variable alpha values for
their colors. D3DFB no longer forces them to 255.
- Updated re2c to version 0.12.3.
- Fixed: A_Wander used threshold as a timer, when it should have used
reactiontime.
- Fixed: A_CustomRailgun would not fire at all for actors without a target
when the aim parameter was disabled.
- Made the warp command work in multiplayer, again courtesy of Karate Chris.
- Fixed: Trying to spawn a bot while not in a game made for a crashing time.
(Patch courtesy of Karate Chris.)
- Removed some floating point math from hu_scores.cpp that somebody's GCC
gave warnings for (not mine, though).
- Fixed: The SBarInfo drawbar command crashed if the sprite image was
unavailable.
- Fixed: FString::operator=(const char *) did not release its old buffer when
being assigned to the null string.
- The scanner no longer has an upper limit on the length of strings it
accepts, though short strings will be faster than long ones.
- Moved all the text scanning functions into a class. Mainly, this means that
multiple script scanner states can be stored without being forced to do so
recursively. I think I might be taking advantage of that in the near
future. Possibly. Maybe.
- Removed some potential buffer overflows from the decal parser.
- Applied Blzut3's SBARINFO update #9:
* Fixed: When using even length values in drawnumber it would cap to a 98
value instead of a 99 as intended.
* The SBarInfo parser can now accept negatives for coordinates. This
doesn't allow much right now, but later I plan to add better fullscreen
hud support in which the negatives will be more useful. This also cleans
up the source a bit since all calls for (x, y) coordinates are with the
function getCoordinates().
- Added support for stencilling actors.
- Added support for non-black colors specified with DTA_ColorOverlay to the
software renderer.
- Fixed: The inverse, gold, red, and green fixed colormaps each allocated
space for 32 different colormaps, even though each only used the first one.
- Added two new blending flags to make reverse subtract blending more useful:
STYLEF_InvertSource and STYLEF_InvertOverlay. These invert the color that
gets blended with the background, since that seems like a good idea for
reverse subtraction. They also work with the other two blending operations.
- Added subtract and reverse subtract blending operations to the renderer.
Since the ERenderStyle enumeration was getting rather unwieldy, I converted
it into a new FRenderStyle structure that lets each parameter of the
blending equation be set separately. This simplified the set up for the
blend quite a bit, and it means a number of new combinations are available
by setting the parameters properly.
SVN r710 (trunk)
2008-01-25 23:57:44 +00:00
|
|
|
info->RenderStyle = ERenderStyle(style);
|
2006-02-24 04:48:15 +00:00
|
|
|
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);
|
|
|
|
|
2006-10-31 14:53:21 +00:00
|
|
|
if (type != NULL && !patchedStates)
|
|
|
|
{
|
2008-09-22 18:55:29 +00:00
|
|
|
statedef.MakeStateDefines(type);
|
2006-10-31 14:53:21 +00:00
|
|
|
patchedStates = true;
|
|
|
|
}
|
|
|
|
|
2006-02-24 04:48:15 +00:00
|
|
|
if (!strnicmp (Line1, "Initial", 7))
|
2008-12-07 12:11:59 +00:00
|
|
|
statedef.SetStateLabel("Spawn", state ? state : GetDefault<AActor>()->SpawnState);
|
2006-02-24 04:48:15 +00:00
|
|
|
else if (!strnicmp (Line1, "First moving", 12))
|
2008-12-07 12:11:59 +00:00
|
|
|
statedef.SetStateLabel("See", state);
|
2006-02-24 04:48:15 +00:00
|
|
|
else if (!strnicmp (Line1, "Injury", 6))
|
2008-12-07 12:11:59 +00:00
|
|
|
statedef.SetStateLabel("Pain", state);
|
2006-02-24 04:48:15 +00:00
|
|
|
else if (!strnicmp (Line1, "Close attack", 12))
|
2006-10-31 14:53:21 +00:00
|
|
|
{
|
|
|
|
if (thingy != 1) // Not for players!
|
|
|
|
{
|
2008-12-07 12:11:59 +00:00
|
|
|
statedef.SetStateLabel("Melee", state);
|
2006-10-31 14:53:21 +00:00
|
|
|
}
|
|
|
|
}
|
2006-02-24 04:48:15 +00:00
|
|
|
else if (!strnicmp (Line1, "Far attack", 10))
|
|
|
|
{
|
|
|
|
if (thingy != 1) // Not for players!
|
|
|
|
{
|
2008-12-07 12:11:59 +00:00
|
|
|
statedef.SetStateLabel("Missile", state);
|
2006-02-24 04:48:15 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
else if (!strnicmp (Line1, "Death", 5))
|
2008-12-07 12:11:59 +00:00
|
|
|
statedef.SetStateLabel("Death", state);
|
2006-02-24 04:48:15 +00:00
|
|
|
else if (!strnicmp (Line1, "Exploding", 9))
|
2008-12-07 12:11:59 +00:00
|
|
|
statedef.SetStateLabel("XDeath", state);
|
2006-02-24 04:48:15 +00:00
|
|
|
else if (!strnicmp (Line1, "Respawn", 7))
|
2008-12-07 12:11:59 +00:00
|
|
|
statedef.SetStateLabel("Raise", state);
|
2006-02-24 04:48:15 +00:00
|
|
|
}
|
|
|
|
else if (stricmp (Line1 + linelen - 6, " sound") == 0)
|
|
|
|
{
|
2008-06-15 02:25:09 +00:00
|
|
|
FSoundID snd;
|
2006-02-24 04:48:15 +00:00
|
|
|
|
2009-03-28 11:49:44 +00:00
|
|
|
if (val == 0 || val >= SoundMap.Size())
|
2006-02-24 04:48:15 +00:00
|
|
|
{
|
|
|
|
if (endptr == Line2)
|
|
|
|
{ // Sound was not a (valid) number,
|
|
|
|
// so treat it as an actual sound name.
|
|
|
|
stripwhite (Line2);
|
2008-06-15 02:25:09 +00:00
|
|
|
snd = Line2;
|
2006-02-24 04:48:15 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2009-03-28 11:49:44 +00:00
|
|
|
snd = SoundMap[val-1];
|
2006-02-24 04:48:15 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
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 };
|
2009-09-14 22:12:31 +00:00
|
|
|
// ZDoom used to block the upper range of bits to force use of mnemonics for extra flags.
|
|
|
|
// MBF also defined extra flags in the same range, but without forcing mnemonics. For MBF
|
|
|
|
// compatibility, the upper bits are freed, but we have conflicts between the ZDoom bits
|
|
|
|
// and the MBF bits. The only such flag exposed to DEHSUPP, though, is STEALTH -- the others
|
|
|
|
// are not available through mnemonics, and aren't available either through their bit value.
|
2010-07-25 21:46:51 +00:00
|
|
|
// So if we find the STEALTH keyword, it's a ZDoom mod, otherwise assume FRIEND.
|
2009-09-14 22:12:31 +00:00
|
|
|
bool zdoomflags = false;
|
2006-02-24 04:48:15 +00:00
|
|
|
char *strval;
|
|
|
|
|
|
|
|
for (strval = Line2; (strval = strtok (strval, ",+| \t\f\r")); strval = NULL)
|
|
|
|
{
|
|
|
|
if (IsNum (strval))
|
|
|
|
{
|
2009-09-15 05:59:55 +00:00
|
|
|
// I have no idea why everyone insists on using strtol here even though it fails
|
|
|
|
// dismally if a value is parsed where the highest bit it set. Do people really
|
|
|
|
// use negative values here? Let's better be safe and check both.
|
|
|
|
if (strchr(strval, '-')) value[0] |= (unsigned long)strtol(strval, NULL, 10);
|
|
|
|
else value[0] |= (unsigned long)strtoul(strval, NULL, 10);
|
2006-02-24 04:48:15 +00:00
|
|
|
vchanged[0] = true;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2009-09-14 22:12:31 +00:00
|
|
|
// STEALTH FRIEND HACK!
|
|
|
|
if (!stricmp(strval, "STEALTH")) zdoomflags = true;
|
2009-03-28 11:49:44 +00:00
|
|
|
unsigned i;
|
|
|
|
for(i = 0; i < BitNames.Size(); i++)
|
2008-03-22 21:07:31 +00:00
|
|
|
{
|
2009-03-28 11:49:44 +00:00
|
|
|
if (!stricmp(strval, BitNames[i].Name))
|
2008-03-23 05:24:40 +00:00
|
|
|
{
|
2009-03-28 11:49:44 +00:00
|
|
|
vchanged[BitNames[i].WhichFlags] = true;
|
|
|
|
value[BitNames[i].WhichFlags] |= 1 << BitNames[i].Bit;
|
|
|
|
break;
|
2008-03-23 05:24:40 +00:00
|
|
|
}
|
2008-03-22 21:07:31 +00:00
|
|
|
}
|
2009-03-28 11:49:44 +00:00
|
|
|
if (i == BitNames.Size())
|
|
|
|
{
|
|
|
|
DPrintf("Unknown bit mnemonic %s\n", strval);
|
|
|
|
}
|
2006-02-24 04:48:15 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
if (vchanged[0])
|
|
|
|
{
|
2009-09-14 22:12:31 +00:00
|
|
|
if (value[0] & MF_SLIDE)
|
|
|
|
{
|
|
|
|
// SLIDE (which occupies in Doom what is the MF_INCHASE slot in ZDoom)
|
|
|
|
value[0] &= ~MF_SLIDE; // clean the slot
|
|
|
|
// Nothing else to do, this flag is never actually used.
|
|
|
|
}
|
2006-02-24 04:48:15 +00:00
|
|
|
if (value[0] & MF_TRANSLATION)
|
|
|
|
{
|
|
|
|
info->Translation = TRANSLATION (TRANSLATION_Standard,
|
|
|
|
((value[0] & MF_TRANSLATION) >> (MF_TRANSSHIFT))-1);
|
|
|
|
value[0] &= ~MF_TRANSLATION;
|
|
|
|
}
|
2009-09-14 22:12:31 +00:00
|
|
|
if (value[0] & MF_TOUCHY)
|
|
|
|
{
|
|
|
|
// TOUCHY (which occupies in MBF what is the MF_UNMORPHED slot in ZDoom)
|
|
|
|
value[0] &= ~MF_TOUCHY; // clean the slot
|
|
|
|
info->flags6 |= MF6_TOUCHY; // remap the flag
|
|
|
|
}
|
|
|
|
if (value[0] & MF_BOUNCES)
|
|
|
|
{
|
|
|
|
// BOUNCES (which occupies in MBF the MF_NOLIFTDROP slot)
|
|
|
|
// This flag is especially convoluted as what it does depend on what
|
|
|
|
// other flags the actor also has, and whether it's "sentient" or not.
|
|
|
|
value[0] &= ~MF_BOUNCES; // clean the slot
|
|
|
|
|
|
|
|
// MBF considers that things that bounce can be damaged, even if not shootable.
|
|
|
|
info->flags6 |= MF6_VULNERABLE;
|
|
|
|
// MBF also considers that bouncers pass through blocking lines as projectiles.
|
|
|
|
info->flags3 |= MF3_NOBLOCKMONST;
|
|
|
|
// MBF also considers that bouncers that explode are grenades, and MBF grenades
|
|
|
|
// are supposed to hurt everything, except cyberdemons if they're fired by cybies.
|
|
|
|
// Let's translate that in a more generic way as grenades which hurt everything
|
|
|
|
// except the class of their shooter. Yes, it does diverge a bit from MBF, as for
|
|
|
|
// example a dehacked arachnotron that shoots grenade would kill itself quickly
|
|
|
|
// in MBF and will not here. But class-specific checks are cumbersome and limiting.
|
|
|
|
info->flags4 |= (MF4_FORCERADIUSDMG | MF4_DONTHARMCLASS);
|
|
|
|
|
|
|
|
// MBF bouncing missiles rebound on floors and ceiling, but not on walls.
|
|
|
|
// This is different from BOUNCE_Heretic behavior as in Heretic the missiles
|
|
|
|
// die when they bounce, while in MBF they will continue to bounce until they
|
|
|
|
// collide with a wall or a solid actor.
|
|
|
|
if (value[0] & MF_MISSILE) info->BounceFlags = BOUNCE_Classic;
|
|
|
|
// MBF bouncing actors that do not have the missile flag will also rebound on
|
|
|
|
// walls, and this does correspond roughly to the ZDoom bounce style.
|
|
|
|
else info->BounceFlags = BOUNCE_Grenade;
|
|
|
|
|
|
|
|
// MBF grenades are dehacked rockets that gain the BOUNCES flag but
|
|
|
|
// lose the MISSILE flag, so they can be identified here easily.
|
|
|
|
if (!(value[0] & MF_MISSILE) && info->effects & FX_ROCKET)
|
|
|
|
{
|
|
|
|
info->effects &= ~FX_ROCKET; // replace rocket trail
|
|
|
|
info->effects |= FX_GRENADE; // by grenade trail
|
|
|
|
}
|
|
|
|
|
|
|
|
// MBF bounce factors depend on flag combos:
|
|
|
|
enum
|
|
|
|
{
|
|
|
|
MBF_BOUNCE_NOGRAVITY = FRACUNIT, // With NOGRAVITY: full momentum
|
|
|
|
MBF_BOUNCE_FLOATDROPOFF = (FRACUNIT * 85) / 100,// With FLOAT and DROPOFF: 85%
|
|
|
|
MBF_BOUNCE_FLOAT = (FRACUNIT * 70) / 100,// With FLOAT alone: 70%
|
|
|
|
MBF_BOUNCE_DEFAULT = (FRACUNIT * 45) / 100,// Without the above flags: 45%
|
|
|
|
MBF_BOUNCE_WALL = (FRACUNIT * 50) / 100,// Bouncing off walls: 50%
|
|
|
|
};
|
|
|
|
info->bouncefactor = ((value[0] & MF_NOGRAVITY) ? MBF_BOUNCE_NOGRAVITY
|
|
|
|
: (value[0] & MF_FLOAT) ? (value[0] & MF_DROPOFF) ? MBF_BOUNCE_FLOATDROPOFF
|
|
|
|
: MBF_BOUNCE_FLOAT : MBF_BOUNCE_DEFAULT);
|
|
|
|
|
|
|
|
info->wallbouncefactor = ((value[0] & MF_NOGRAVITY) ? MBF_BOUNCE_NOGRAVITY : MBF_BOUNCE_WALL);
|
|
|
|
|
|
|
|
// MBF sentient actors with BOUNCE and FLOAT are able to "jump" by floating up.
|
|
|
|
if (info->IsSentient())
|
|
|
|
{
|
|
|
|
if (value[0] & MF_FLOAT) info->flags6 |= MF6_CANJUMP;
|
|
|
|
}
|
|
|
|
// Non sentient actors can be damaged but they shouldn't bleed.
|
|
|
|
else
|
|
|
|
{
|
|
|
|
value[0] |= MF_NOBLOOD;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (zdoomflags && (value [0] & MF_STEALTH))
|
|
|
|
{
|
|
|
|
// STEALTH FRIEND HACK!
|
|
|
|
}
|
|
|
|
else if (value[0] & MF_FRIEND)
|
|
|
|
{
|
|
|
|
// FRIEND (which occupies in MBF the MF_STEALTH slot)
|
|
|
|
value[0] &= ~MF_FRIEND; // clean the slot
|
|
|
|
value[0] |= MF_FRIENDLY; // remap the flag to its ZDoom equivalent
|
|
|
|
// MBF friends are not blocked by monster blocking lines:
|
|
|
|
info->flags3 |= MF3_NOBLOCKMONST;
|
|
|
|
}
|
|
|
|
if (value[0] & MF_TRANSLUCENT)
|
|
|
|
{
|
|
|
|
// TRANSLUCENT (which occupies in Boom the MF_ICECORPSE slot)
|
|
|
|
value[0] &= ~MF_TRANSLUCENT; // clean the slot
|
|
|
|
vchanged[2] = true; value[2] |= 2; // let the TRANSLUCxx code below handle it
|
|
|
|
}
|
2011-12-06 08:36:28 +00:00
|
|
|
if ((info->flags & MF_MISSILE) && (info->flags2 & MF2_NOTELEPORT)
|
|
|
|
&& !(value[0] & MF_MISSILE))
|
|
|
|
{
|
|
|
|
// ZDoom gives missiles flags that did not exist in Doom: MF2_NOTELEPORT,
|
|
|
|
// MF2_IMPACT, and MF2_PCROSS. The NOTELEPORT one can be a problem since
|
|
|
|
// some projectile actors (those new to Doom II) were not excluded from
|
|
|
|
// triggering line effects and can teleport when the missile flag is removed.
|
|
|
|
info->flags2 &= ~MF2_NOTELEPORT;
|
|
|
|
}
|
2006-02-24 04:48:15 +00:00
|
|
|
info->flags = value[0];
|
|
|
|
}
|
|
|
|
if (vchanged[1])
|
|
|
|
{
|
|
|
|
info->flags2 = value[1];
|
2009-05-23 08:30:36 +00:00
|
|
|
if (info->flags2 & 0x00000004) // old BOUNCE1
|
|
|
|
{
|
|
|
|
info->flags2 &= ~4;
|
2009-09-06 01:49:17 +00:00
|
|
|
info->BounceFlags = BOUNCE_DoomCompat;
|
2006-02-24 04:48:15 +00:00
|
|
|
}
|
|
|
|
// Damage types that once were flags but now are not
|
|
|
|
if (info->flags2 & 0x20000000)
|
|
|
|
{
|
2006-10-31 14:53:21 +00:00
|
|
|
info->DamageType = NAME_Ice;
|
2006-02-24 04:48:15 +00:00
|
|
|
info->flags2 &= ~0x20000000;
|
|
|
|
}
|
|
|
|
if (info->flags2 & 0x10000)
|
|
|
|
{
|
2006-10-31 14:53:21 +00:00
|
|
|
info->DamageType = NAME_Fire;
|
2006-02-24 04:48:15 +00:00
|
|
|
info->flags2 &= ~0x10000;
|
|
|
|
}
|
2007-01-20 14:27:44 +00:00
|
|
|
if (info->flags2 & 1)
|
|
|
|
{
|
|
|
|
info->gravity = FRACUNIT/4;
|
|
|
|
info->flags2 &= ~1;
|
|
|
|
}
|
2006-02-24 04:48:15 +00:00
|
|
|
}
|
|
|
|
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;
|
|
|
|
}
|
2006-09-14 00:02:31 +00:00
|
|
|
DPrintf ("Bits: %d,%d (0x%08x,0x%08x)\n", info->flags, info->flags2,
|
|
|
|
info->flags, info->flags2);
|
2006-02-24 04:48:15 +00:00
|
|
|
}
|
|
|
|
else if (stricmp (Line1, "ID #") == 0)
|
|
|
|
{
|
2009-05-15 10:39:40 +00:00
|
|
|
*ednum = (SWORD)val;
|
2006-02-24 04:48:15 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
else Printf (unknown_str, Line1, "Thing", thingy);
|
|
|
|
}
|
|
|
|
|
2008-03-13 00:45:35 +00:00
|
|
|
if (info != (AActor *)&dummy)
|
2006-02-24 04:48:15 +00:00
|
|
|
{
|
|
|
|
// Reset heights for things hanging from the ceiling that
|
|
|
|
// don't specify a new height.
|
|
|
|
if (info->flags & MF_SPAWNCEILING &&
|
|
|
|
!hadHeight &&
|
2009-04-08 04:14:37 +00:00
|
|
|
thingy <= (int)OrgHeights.Size() && thingy > 0)
|
2006-02-24 04:48:15 +00:00
|
|
|
{
|
|
|
|
info->height = OrgHeights[thingy - 1] * FRACUNIT;
|
2008-10-05 11:23:41 +00:00
|
|
|
info->projectilepassheight = 0;
|
2006-02-24 04:48:15 +00:00
|
|
|
}
|
|
|
|
// 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)
|
|
|
|
{
|
2008-08-13 09:11:19 +00:00
|
|
|
PushTouchedActor(const_cast<PClass *>(type));
|
2006-02-24 04:48:15 +00:00
|
|
|
}
|
|
|
|
|
2012-02-23 03:24:13 +00:00
|
|
|
// If MF_COUNTKILL is set, make sure the other standard monster flags are
|
|
|
|
// set, too. And vice versa.
|
2012-07-06 03:04:06 +00:00
|
|
|
if (thingy != 1) // don't mess with the player's flags
|
2006-02-24 04:48:15 +00:00
|
|
|
{
|
2012-07-06 03:04:06 +00:00
|
|
|
if (info->flags & MF_COUNTKILL)
|
|
|
|
{
|
|
|
|
info->flags2 |= MF2_PUSHWALL | MF2_MCROSS | MF2_PASSMOBJ;
|
|
|
|
info->flags3 |= MF3_ISMONSTER;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
info->flags2 &= ~(MF2_PUSHWALL | MF2_MCROSS);
|
|
|
|
info->flags3 &= ~MF3_ISMONSTER;
|
|
|
|
}
|
2006-02-24 04:48:15 +00:00
|
|
|
}
|
2012-02-23 03:24:13 +00:00
|
|
|
// Everything that's altered here gets the CANUSEWALLS flag, just in case
|
|
|
|
// it calls P_Move().
|
|
|
|
info->flags4 |= MF4_CANUSEWALLS;
|
2006-10-31 14:53:21 +00:00
|
|
|
if (patchedStates)
|
|
|
|
{
|
2008-09-22 18:55:29 +00:00
|
|
|
statedef.InstallStates(type->ActorInfo, info);
|
2006-10-31 14:53:21 +00:00
|
|
|
}
|
2006-02-24 04:48:15 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
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)
|
|
|
|
{
|
2012-08-23 01:00:30 +00:00
|
|
|
tics = clamp (val, -1, SHRT_MAX);
|
2006-02-24 04:48:15 +00:00
|
|
|
}
|
|
|
|
else if (keylen == 9 && stricmp (Line1, "Unknown 1") == 0)
|
|
|
|
{
|
2009-09-14 22:12:31 +00:00
|
|
|
misc1 = val;
|
2006-02-24 04:48:15 +00:00
|
|
|
}
|
|
|
|
else if (keylen == 9 && stricmp (Line1, "Unknown 2") == 0)
|
|
|
|
{
|
2009-09-14 22:12:31 +00:00
|
|
|
info->Misc2 = val;
|
2006-02-24 04:48:15 +00:00
|
|
|
}
|
|
|
|
else if (keylen == 13 && stricmp (Line1, "Sprite number") == 0)
|
|
|
|
{
|
|
|
|
unsigned int i;
|
|
|
|
|
2009-04-08 04:14:37 +00:00
|
|
|
if (val < (int)OrgSprNames.Size())
|
2006-02-24 04:48:15 +00:00
|
|
|
{
|
|
|
|
for (i = 0; i < sprites.Size(); i++)
|
|
|
|
{
|
2009-03-28 11:49:44 +00:00
|
|
|
if (memcmp (OrgSprNames[val].c, sprites[i].name, 4) == 0)
|
2006-02-24 04:48:15 +00:00
|
|
|
{
|
2008-08-10 14:19:47 +00:00
|
|
|
info->sprite = (int)i;
|
2006-02-24 04:48:15 +00:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (i == sprites.Size ())
|
|
|
|
{
|
|
|
|
Printf ("Frame %d: Sprite %d (%s) is undefined\n",
|
2009-03-28 11:49:44 +00:00
|
|
|
frameNum, val, OrgSprNames[val].c);
|
2006-02-24 04:48:15 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
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)
|
|
|
|
{
|
2009-09-14 22:12:31 +00:00
|
|
|
info->DefineFlags |= SDF_DEHACKED; // Signals the state has been modified by dehacked
|
2006-02-24 04:48:15 +00:00
|
|
|
if ((unsigned)(frame & 0x7fff) > 63)
|
|
|
|
{
|
|
|
|
Printf ("Frame %d: Subnumber must be in range [0,63]\n", frameNum);
|
|
|
|
}
|
2008-08-10 14:19:47 +00:00
|
|
|
info->Tics = tics;
|
|
|
|
info->Misc1 = misc1;
|
2010-04-19 02:46:50 +00:00
|
|
|
info->Frame = frame & 0x3f;
|
|
|
|
info->Fullbright = frame & 0x8000 ? true : false;
|
2006-02-24 04:48:15 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int PatchSprite (int sprNum)
|
|
|
|
{
|
|
|
|
int result;
|
|
|
|
int offset = 0;
|
|
|
|
|
2009-03-28 11:49:44 +00:00
|
|
|
if ((unsigned)sprNum < OrgSprNames.Size())
|
2006-02-24 04:48:15 +00:00
|
|
|
{
|
|
|
|
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;
|
2009-03-28 11:49:44 +00:00
|
|
|
|
|
|
|
if ((unsigned)offset < OrgSprNames.Size())
|
2006-02-24 04:48:15 +00:00
|
|
|
{
|
2009-03-28 11:49:44 +00:00
|
|
|
sprNum = FindSprite (OrgSprNames[sprNum].c);
|
2006-02-24 04:48:15 +00:00
|
|
|
if (sprNum != -1)
|
2009-03-28 11:49:44 +00:00
|
|
|
strncpy (sprites[sprNum].name, OrgSprNames[offset].c, 4);
|
2006-02-24 04:48:15 +00:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
Printf ("Sprite name %d out of range.\n", offset);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int PatchAmmo (int ammoNum)
|
|
|
|
{
|
2009-03-28 11:49:44 +00:00
|
|
|
const PClass *ammoType = NULL;
|
|
|
|
AAmmo *defaultAmmo = NULL;
|
2006-02-24 04:48:15 +00:00
|
|
|
int result;
|
|
|
|
int oldclip;
|
|
|
|
int dummy;
|
2009-03-28 11:49:44 +00:00
|
|
|
int *max = &dummy;
|
|
|
|
int *per = &dummy;
|
2006-02-24 04:48:15 +00:00
|
|
|
|
2009-03-28 11:49:44 +00:00
|
|
|
if (ammoNum >= 0 && ammoNum < 4 && (unsigned)ammoNum <= AmmoNames.Size())
|
2006-02-24 04:48:15 +00:00
|
|
|
{
|
|
|
|
DPrintf ("Ammo %d.\n", ammoNum);
|
2009-03-28 11:49:44 +00:00
|
|
|
ammoType = AmmoNames[ammoNum];
|
|
|
|
if (ammoType != NULL)
|
|
|
|
{
|
|
|
|
defaultAmmo = (AAmmo *)GetDefaultByType (ammoType);
|
|
|
|
if (defaultAmmo != NULL)
|
|
|
|
{
|
|
|
|
max = &defaultAmmo->MaxAmount;
|
|
|
|
per = &defaultAmmo->Amount;
|
|
|
|
}
|
|
|
|
}
|
2006-02-24 04:48:15 +00:00
|
|
|
}
|
2009-03-28 11:49:44 +00:00
|
|
|
|
|
|
|
if (ammoType == NULL)
|
2006-02-24 04:48:15 +00:00
|
|
|
{
|
|
|
|
Printf ("Ammo %d out of range.\n", ammoNum);
|
|
|
|
}
|
|
|
|
|
|
|
|
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;
|
2009-03-28 11:49:44 +00:00
|
|
|
const PClass *type = NULL;
|
2006-02-24 04:48:15 +00:00
|
|
|
BYTE dummy[sizeof(AWeapon)];
|
2009-03-28 11:49:44 +00:00
|
|
|
AWeapon *info = (AWeapon *)&dummy;
|
2006-10-31 14:53:21 +00:00
|
|
|
bool patchedStates = false;
|
2008-09-22 18:55:29 +00:00
|
|
|
FStateDefinitions statedef;
|
2006-02-24 04:48:15 +00:00
|
|
|
|
2009-03-28 11:49:44 +00:00
|
|
|
if (weapNum >= 0 && weapNum < 9 && (unsigned)weapNum < WeaponNames.Size())
|
2006-02-24 04:48:15 +00:00
|
|
|
{
|
2009-03-28 11:49:44 +00:00
|
|
|
type = WeaponNames[weapNum];
|
|
|
|
if (type != NULL)
|
|
|
|
{
|
|
|
|
info = (AWeapon *)GetDefaultByType (type);
|
|
|
|
DPrintf ("Weapon %d\n", weapNum);
|
|
|
|
}
|
2006-02-24 04:48:15 +00:00
|
|
|
}
|
2009-03-28 11:49:44 +00:00
|
|
|
|
|
|
|
if (type == NULL)
|
2006-02-24 04:48:15 +00:00
|
|
|
{
|
|
|
|
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);
|
|
|
|
|
2006-10-31 14:53:21 +00:00
|
|
|
if (type != NULL && !patchedStates)
|
|
|
|
{
|
2008-09-22 18:55:29 +00:00
|
|
|
statedef.MakeStateDefines(type);
|
2006-10-31 14:53:21 +00:00
|
|
|
patchedStates = true;
|
|
|
|
}
|
|
|
|
|
2006-02-24 04:48:15 +00:00
|
|
|
if (strnicmp (Line1, "Deselect", 8) == 0)
|
2008-12-07 12:11:59 +00:00
|
|
|
statedef.SetStateLabel("Select", state);
|
2006-02-24 04:48:15 +00:00
|
|
|
else if (strnicmp (Line1, "Select", 6) == 0)
|
2008-12-07 12:11:59 +00:00
|
|
|
statedef.SetStateLabel("Deselect", state);
|
2006-02-24 04:48:15 +00:00
|
|
|
else if (strnicmp (Line1, "Bobbing", 7) == 0)
|
2008-12-07 12:11:59 +00:00
|
|
|
statedef.SetStateLabel("Ready", state);
|
2006-02-24 04:48:15 +00:00
|
|
|
else if (strnicmp (Line1, "Shooting", 8) == 0)
|
2008-12-07 12:11:59 +00:00
|
|
|
statedef.SetStateLabel("Fire", state);
|
2006-02-24 04:48:15 +00:00
|
|
|
else if (strnicmp (Line1, "Firing", 6) == 0)
|
2008-12-07 12:11:59 +00:00
|
|
|
statedef.SetStateLabel("Flash", state);
|
2006-02-24 04:48:15 +00:00
|
|
|
}
|
|
|
|
else if (stricmp (Line1, "Ammo type") == 0)
|
|
|
|
{
|
2009-03-28 11:49:44 +00:00
|
|
|
if (val < 0 || val >= 12 || (unsigned)val >= AmmoNames.Size())
|
2006-02-24 04:48:15 +00:00
|
|
|
{
|
|
|
|
val = 5;
|
|
|
|
}
|
2009-03-28 11:49:44 +00:00
|
|
|
info->AmmoType1 = 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;
|
2012-04-07 11:33:35 +00:00
|
|
|
info->flags6 |= MF6_INTRYMOVE; // flag the weapon for postprocessing (reuse a flag that can't be set by external means)
|
2006-02-24 04:48:15 +00:00
|
|
|
}
|
|
|
|
else if (stricmp (Line1, "Min ammo") == 0)
|
|
|
|
{
|
|
|
|
info->MinAmmo1 = val;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
Printf (unknown_str, Line1, "Weapon", weapNum);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (info->AmmoType1 == NULL)
|
|
|
|
{
|
|
|
|
info->AmmoUse1 = 0;
|
|
|
|
}
|
|
|
|
|
2006-10-31 14:53:21 +00:00
|
|
|
if (patchedStates)
|
|
|
|
{
|
2008-09-22 18:55:29 +00:00
|
|
|
statedef.InstallStates(type->ActorInfo, info);
|
2006-10-31 14:53:21 +00:00
|
|
|
}
|
|
|
|
|
2006-02-24 04:48:15 +00:00
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
2009-09-14 22:12:31 +00:00
|
|
|
static void SetPointer(FState *state, PSymbol *sym, int frame = 0)
|
2007-04-28 09:06:32 +00:00
|
|
|
{
|
2009-03-28 11:49:44 +00:00
|
|
|
if (sym==NULL || sym->SymbolType != SYM_ActionFunction)
|
2007-04-28 09:06:32 +00:00
|
|
|
{
|
2008-08-12 14:30:07 +00:00
|
|
|
state->SetAction(NULL);
|
2009-09-14 22:12:31 +00:00
|
|
|
return;
|
2007-04-28 09:06:32 +00:00
|
|
|
}
|
2009-03-28 11:49:44 +00:00
|
|
|
else
|
2007-04-28 09:06:32 +00:00
|
|
|
{
|
2009-09-15 17:19:30 +00:00
|
|
|
FString symname = sym->SymbolName.GetChars();
|
2008-08-12 14:30:07 +00:00
|
|
|
state->SetAction(static_cast<PSymbolActionFunction*>(sym));
|
2009-09-14 22:12:31 +00:00
|
|
|
|
|
|
|
// Note: CompareNoCase() calls stricmp() and therefore returns 0 when they're the same.
|
|
|
|
for (unsigned int i = 0; i < MBFCodePointers.Size(); i++)
|
|
|
|
{
|
|
|
|
if (!symname.CompareNoCase(MBFCodePointers[i].name))
|
|
|
|
{
|
2009-09-15 23:31:28 +00:00
|
|
|
MBFParamState newstate;
|
|
|
|
newstate.state = state;
|
|
|
|
newstate.pointer = i;
|
2009-09-14 22:12:31 +00:00
|
|
|
MBFParamStates.Push(newstate);
|
|
|
|
break; // No need to cycle through the rest of the list.
|
|
|
|
}
|
|
|
|
}
|
2007-04-28 09:06:32 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2006-02-24 04:48:15 +00:00
|
|
|
static int PatchPointer (int ptrNum)
|
|
|
|
{
|
|
|
|
int result;
|
|
|
|
|
2009-08-30 10:43:51 +00:00
|
|
|
// Hack for some Boom dehacked patches that are of the form Pointer 0 (x statenumber)
|
|
|
|
char * key;
|
|
|
|
int indexnum;
|
|
|
|
key=strchr(Line2, '(');
|
|
|
|
if (key++) key=strchr(key, ' '); else key=NULL;
|
|
|
|
if ((ptrNum == 0) && key++)
|
|
|
|
{
|
|
|
|
*strchr(key, ')') = '\0';
|
|
|
|
indexnum = atoi(key);
|
|
|
|
for (ptrNum = 0; (unsigned int) ptrNum < CodePConv.Size(); ++ptrNum)
|
|
|
|
{
|
|
|
|
if (CodePConv[ptrNum] == indexnum) break;
|
|
|
|
}
|
|
|
|
DPrintf("Final ptrNum: %i\n", ptrNum);
|
|
|
|
}
|
|
|
|
// End of hack.
|
|
|
|
|
|
|
|
// 448 Doom states with codepointers + 74 extra MBF states with codepointers = 522 total
|
|
|
|
// Better to just use the size of the array rather than a hardcoded value.
|
|
|
|
if (ptrNum >= 0 && (unsigned int) ptrNum < CodePConv.Size())
|
|
|
|
{
|
2006-02-24 04:48:15 +00:00
|
|
|
DPrintf ("Pointer %d\n", ptrNum);
|
2009-08-30 10:43:51 +00:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2006-02-24 04:48:15 +00:00
|
|
|
Printf ("Pointer %d out of range.\n", ptrNum);
|
|
|
|
ptrNum = -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
while ((result = GetLine ()) == 1)
|
|
|
|
{
|
2009-03-28 11:49:44 +00:00
|
|
|
if ((unsigned)ptrNum < CodePConv.Size() && (!stricmp (Line1, "Codep Frame")))
|
2006-02-24 04:48:15 +00:00
|
|
|
{
|
|
|
|
FState *state = FindState (CodePConv[ptrNum]);
|
|
|
|
if (state)
|
|
|
|
{
|
2007-04-28 09:06:32 +00:00
|
|
|
int index = atoi(Line2);
|
2009-03-28 11:49:44 +00:00
|
|
|
if ((unsigned)(index) >= Actions.Size())
|
|
|
|
SetPointer(state, NULL);
|
2006-02-24 04:48:15 +00:00
|
|
|
else
|
2007-04-28 09:06:32 +00:00
|
|
|
{
|
2009-09-14 22:12:31 +00:00
|
|
|
SetPointer(state, Actions[index], CodePConv[ptrNum]);
|
2007-04-28 09:06:32 +00:00
|
|
|
}
|
2009-09-14 22:12:31 +00:00
|
|
|
DPrintf("%s has a hacked state for pointer num %i with index %i\nLine1=%s, Line2=%s\n",
|
|
|
|
state->StaticFindStateOwner(state)->TypeName.GetChars(), ptrNum, index, Line1, Line2);
|
2006-02-24 04:48:15 +00:00
|
|
|
}
|
|
|
|
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[] = {
|
2013-07-31 10:26:47 +00:00
|
|
|
{ "Initial Health", static_cast<ptrdiff_t>(myoffsetof(struct DehInfo,StartHealth)) },
|
|
|
|
{ "Initial Bullets", static_cast<ptrdiff_t>(myoffsetof(struct DehInfo,StartBullets)) },
|
|
|
|
{ "Max Health", static_cast<ptrdiff_t>(myoffsetof(struct DehInfo,MaxHealth)) },
|
|
|
|
{ "Max Armor", static_cast<ptrdiff_t>(myoffsetof(struct DehInfo,MaxArmor)) },
|
|
|
|
{ "Green Armor Class", static_cast<ptrdiff_t>(myoffsetof(struct DehInfo,GreenAC)) },
|
|
|
|
{ "Blue Armor Class", static_cast<ptrdiff_t>(myoffsetof(struct DehInfo,BlueAC)) },
|
|
|
|
{ "Max Soulsphere", static_cast<ptrdiff_t>(myoffsetof(struct DehInfo,MaxSoulsphere)) },
|
|
|
|
{ "Soulsphere Health", static_cast<ptrdiff_t>(myoffsetof(struct DehInfo,SoulsphereHealth)) },
|
|
|
|
{ "Megasphere Health", static_cast<ptrdiff_t>(myoffsetof(struct DehInfo,MegasphereHealth)) },
|
|
|
|
{ "God Mode Health", static_cast<ptrdiff_t>(myoffsetof(struct DehInfo,GodHealth)) },
|
|
|
|
{ "IDFA Armor", static_cast<ptrdiff_t>(myoffsetof(struct DehInfo,FAArmor)) },
|
|
|
|
{ "IDFA Armor Class", static_cast<ptrdiff_t>(myoffsetof(struct DehInfo,FAAC)) },
|
|
|
|
{ "IDKFA Armor", static_cast<ptrdiff_t>(myoffsetof(struct DehInfo,KFAArmor)) },
|
|
|
|
{ "IDKFA Armor Class", static_cast<ptrdiff_t>(myoffsetof(struct DehInfo,KFAAC)) },
|
|
|
|
{ "No Autofreeze", static_cast<ptrdiff_t>(myoffsetof(struct DehInfo,NoAutofreeze)) },
|
2011-03-29 05:20:33 +00:00
|
|
|
{ NULL, 0 }
|
2006-02-24 04:48:15 +00:00
|
|
|
};
|
|
|
|
int result;
|
|
|
|
|
|
|
|
DPrintf ("Misc\n");
|
|
|
|
|
|
|
|
while ((result = GetLine()) == 1)
|
|
|
|
{
|
|
|
|
if (HandleKey (keys, &deh, Line1, atoi (Line2)))
|
|
|
|
{
|
|
|
|
if (stricmp (Line1, "BFG Cells/Shot") == 0)
|
|
|
|
{
|
2012-04-07 11:33:35 +00:00
|
|
|
deh.BFGCells = atoi (Line2);
|
2006-02-24 04:48:15 +00:00
|
|
|
}
|
|
|
|
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);
|
|
|
|
}
|
2009-09-22 08:06:52 +00:00
|
|
|
else if (a > 0)
|
2006-02-24 04:48:15 +00:00
|
|
|
{
|
|
|
|
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));
|
|
|
|
}
|
2009-09-22 08:06:52 +00:00
|
|
|
else
|
|
|
|
{
|
|
|
|
static_cast<APowerup *>(GetDefaultByType (types[i]))->BlendColor = 0;
|
|
|
|
}
|
2006-02-24 04:48:15 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
Printf ("Unknown miscellaneous info %s.\n", Line1);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2006-11-04 13:06:42 +00:00
|
|
|
// Update default item properties by patching the affected items
|
|
|
|
// Note: This won't have any effect on DECORATE derivates of these items!
|
2006-02-24 04:48:15 +00:00
|
|
|
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-09-09 08:55:47 +00:00
|
|
|
if (health!=NULL)
|
2006-05-03 14:54:48 +00:00
|
|
|
{
|
2006-09-09 08:55:47 +00:00
|
|
|
health->MaxAmount = 2 * deh.MaxHealth;
|
2006-05-03 14:54:48 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
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-11-04 13:06:42 +00:00
|
|
|
health = static_cast<AHealth *> (GetDefaultByName ("MegasphereHealth"));
|
|
|
|
if (health!=NULL)
|
|
|
|
{
|
|
|
|
health->Amount = health->MaxAmount = deh.MegasphereHealth;
|
|
|
|
}
|
|
|
|
|
2006-07-13 10:17:56 +00:00
|
|
|
APlayerPawn *player = static_cast<APlayerPawn *> (GetDefaultByName ("DoomPlayer"));
|
|
|
|
if (player != NULL)
|
|
|
|
{
|
|
|
|
player->health = deh.StartHealth;
|
2006-11-05 21:46:28 +00:00
|
|
|
|
2008-09-21 18:02:38 +00:00
|
|
|
// Hm... I'm not sure that this is the right way to change this info...
|
2011-02-19 08:59:43 +00:00
|
|
|
int index = PClass::FindClass(NAME_DoomPlayer)->Meta.GetMetaInt (ACMETA_DropItems) - 1;
|
|
|
|
if (index >= 0 && index < (signed)DropItemList.Size())
|
2006-11-05 21:46:28 +00:00
|
|
|
{
|
2008-09-21 18:02:38 +00:00
|
|
|
FDropItem * di = DropItemList[index];
|
|
|
|
while (di != NULL)
|
2006-11-05 21:46:28 +00:00
|
|
|
{
|
2008-09-21 18:02:38 +00:00
|
|
|
if (di->Name == NAME_Clip)
|
|
|
|
{
|
|
|
|
di->amount = deh.StartBullets;
|
|
|
|
}
|
|
|
|
di = di->Next;
|
2006-11-05 21:46:28 +00:00
|
|
|
}
|
|
|
|
}
|
2006-07-13 10:17:56 +00:00
|
|
|
}
|
|
|
|
|
2009-03-28 11:49:44 +00:00
|
|
|
|
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
|
About a week's worth of changes here. As a heads-up, I wouldn't be
surprised if this doesn't build in Linux right now. The CMakeLists.txt
were checked with MinGW and NMake, but how they fair under Linux is an
unknown to me at this time.
- Converted most sprintf (and all wsprintf) calls to either mysnprintf or
FStrings, depending on the situation.
- Changed the strings in the wbstartstruct to be FStrings.
- Changed myvsnprintf() to output nothing if count is greater than INT_MAX.
This is so that I can use a series of mysnprintf() calls and advance the
pointer for each one. Once the pointer goes beyond the end of the buffer,
the count will go negative, but since it's an unsigned type it will be
seen as excessively huge instead. This should not be a problem, as there's
no reason for ZDoom to be using text buffers larger than 2 GB anywhere.
- Ripped out the disabled bit from FGameConfigFile::MigrateOldConfig().
- Changed CalcMapName() to return an FString instead of a pointer to a static
buffer.
- Changed startmap in d_main.cpp into an FString.
- Changed CheckWarpTransMap() to take an FString& as the first argument.
- Changed d_mapname in g_level.cpp into an FString.
- Changed DoSubstitution() in ct_chat.cpp to place the substitutions in an
FString.
- Fixed: The MAPINFO parser wrote into the string buffer to construct a map
name when given a Hexen map number. This was fine with the old scanner
code, but only a happy coincidence prevents it from crashing with the new
code
- Added the 'B' conversion specifier to StringFormat::VWorker() for printing
binary numbers.
- Added CMake support for building with MinGW, MSYS, and NMake. Linux support
is probably broken until I get around to booting into Linux again. Niceties
provided over the existing Makefiles they're replacing:
* All command-line builds can use the same build system, rather than having
a separate one for MinGW and another for Linux.
* Microsoft's NMake tool is supported as a target.
* Progress meters.
* Parallel makes work from a fresh checkout without needing to be primed
first with a single-threaded make.
* Porting to other architectures should be simplified, whenever that day
comes.
- Replaced the makewad tool with zipdir. This handles the dependency tracking
itself instead of generating an external makefile to do it, since I couldn't
figure out how to generate a makefile with an external tool and include it
with a CMake-generated makefile. Where makewad used a master list of files
to generate the package file, zipdir just zips the entire contents of one or
more directories.
- Added the gdtoa package from netlib's fp library so that ZDoom's printf-style
formatting can be entirely independant of the CRT.
SVN r1082 (trunk)
2008-07-23 04:57:26 +00:00
|
|
|
mysnprintf (mapname, countof(mapname), "E%cM%c", *Line2, *space);
|
2006-02-24 04:48:15 +00:00
|
|
|
par = atoi (moredata + 1);
|
|
|
|
} else {
|
|
|
|
// Only 2 items, must be MAP?? format
|
About a week's worth of changes here. As a heads-up, I wouldn't be
surprised if this doesn't build in Linux right now. The CMakeLists.txt
were checked with MinGW and NMake, but how they fair under Linux is an
unknown to me at this time.
- Converted most sprintf (and all wsprintf) calls to either mysnprintf or
FStrings, depending on the situation.
- Changed the strings in the wbstartstruct to be FStrings.
- Changed myvsnprintf() to output nothing if count is greater than INT_MAX.
This is so that I can use a series of mysnprintf() calls and advance the
pointer for each one. Once the pointer goes beyond the end of the buffer,
the count will go negative, but since it's an unsigned type it will be
seen as excessively huge instead. This should not be a problem, as there's
no reason for ZDoom to be using text buffers larger than 2 GB anywhere.
- Ripped out the disabled bit from FGameConfigFile::MigrateOldConfig().
- Changed CalcMapName() to return an FString instead of a pointer to a static
buffer.
- Changed startmap in d_main.cpp into an FString.
- Changed CheckWarpTransMap() to take an FString& as the first argument.
- Changed d_mapname in g_level.cpp into an FString.
- Changed DoSubstitution() in ct_chat.cpp to place the substitutions in an
FString.
- Fixed: The MAPINFO parser wrote into the string buffer to construct a map
name when given a Hexen map number. This was fine with the old scanner
code, but only a happy coincidence prevents it from crashing with the new
code
- Added the 'B' conversion specifier to StringFormat::VWorker() for printing
binary numbers.
- Added CMake support for building with MinGW, MSYS, and NMake. Linux support
is probably broken until I get around to booting into Linux again. Niceties
provided over the existing Makefiles they're replacing:
* All command-line builds can use the same build system, rather than having
a separate one for MinGW and another for Linux.
* Microsoft's NMake tool is supported as a target.
* Progress meters.
* Parallel makes work from a fresh checkout without needing to be primed
first with a single-threaded make.
* Porting to other architectures should be simplified, whenever that day
comes.
- Replaced the makewad tool with zipdir. This handles the dependency tracking
itself instead of generating an external makefile to do it, since I couldn't
figure out how to generate a makefile with an external tool and include it
with a CMake-generated makefile. Where makewad used a master list of files
to generate the package file, zipdir just zips the entire contents of one or
more directories.
- Added the gdtoa package from netlib's fp library so that ZDoom's printf-style
formatting can be entirely independant of the CRT.
SVN r1082 (trunk)
2008-07-23 04:57:26 +00:00
|
|
|
mysnprintf (mapname, countof(mapname), "MAP%02d", atoi(Line2) % 100);
|
2006-02-24 04:48:15 +00:00
|
|
|
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);
|
|
|
|
|
2009-03-28 11:49:44 +00:00
|
|
|
stripwhite (Line2);
|
2006-02-24 04:48:15 +00:00
|
|
|
if (state == NULL)
|
|
|
|
{
|
|
|
|
Printf ("Frame %d out of range\n", frame);
|
|
|
|
}
|
2009-03-28 11:49:44 +00:00
|
|
|
else if (!stricmp(Line2, "NULL"))
|
|
|
|
{
|
|
|
|
SetPointer(state, NULL);
|
|
|
|
}
|
2006-02-24 04:48:15 +00:00
|
|
|
else
|
|
|
|
{
|
2009-03-28 11:49:44 +00:00
|
|
|
FString symname;
|
2006-02-24 04:48:15 +00:00
|
|
|
|
|
|
|
|
|
|
|
if ((Line2[0] == 'A' || Line2[0] == 'a') && Line2[1] == '_')
|
2009-03-28 11:49:44 +00:00
|
|
|
symname = Line2;
|
2006-02-24 04:48:15 +00:00
|
|
|
else
|
2009-03-28 11:49:44 +00:00
|
|
|
symname.Format("A_%s", Line2);
|
2006-02-24 04:48:15 +00:00
|
|
|
|
2009-09-14 22:12:31 +00:00
|
|
|
// Let's consider as aliases some redundant MBF pointer
|
|
|
|
for (unsigned int i = 0; i < MBFCodePointers.Size(); i++)
|
|
|
|
{
|
|
|
|
if (!symname.CompareNoCase(MBFCodePointers[i].alias))
|
|
|
|
{
|
|
|
|
symname = MBFCodePointers[i].name;
|
|
|
|
Printf("%s --> %s\n", MBFCodePointers[i].alias, MBFCodePointers[i].name);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2009-03-28 11:49:44 +00:00
|
|
|
// This skips the action table and goes directly to the internal symbol table
|
|
|
|
// DEH compatible functions are easy to recognize.
|
|
|
|
PSymbol *sym = RUNTIME_CLASS(AInventory)->Symbols.FindSymbol(symname, true);
|
|
|
|
if (sym == NULL || sym->SymbolType != SYM_ActionFunction)
|
2006-02-24 04:48:15 +00:00
|
|
|
{
|
2009-03-28 11:49:44 +00:00
|
|
|
Printf("Frame %d: Unknown code pointer '%s'\n", frame, Line2);
|
2006-02-24 04:48:15 +00:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2009-03-28 11:49:44 +00:00
|
|
|
FString &args = static_cast<PSymbolActionFunction*>(sym)->Arguments;
|
|
|
|
if (args.Len()!=0 && (args[0]<'a' || args[0]>'z'))
|
2008-03-23 05:24:40 +00:00
|
|
|
{
|
2009-03-28 11:49:44 +00:00
|
|
|
Printf("Frame %d: Incompatible code pointer '%s'\n", frame, Line2);
|
|
|
|
sym = NULL;
|
2006-02-24 04:48:15 +00:00
|
|
|
}
|
|
|
|
}
|
2009-09-14 22:12:31 +00:00
|
|
|
SetPointer(state, sym, frame);
|
2006-02-24 04:48:15 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
2009-02-08 11:28:30 +00:00
|
|
|
static int PatchMusic (int dummy)
|
|
|
|
{
|
|
|
|
int result;
|
|
|
|
|
|
|
|
DPrintf ("[Music]\n");
|
|
|
|
|
|
|
|
while ((result = GetLine()) == 1)
|
|
|
|
{
|
|
|
|
const char *newname = skipwhite (Line2);
|
|
|
|
FString keystring;
|
|
|
|
|
|
|
|
keystring << "MUSIC_" << Line1;
|
|
|
|
|
|
|
|
GStrings.SetString (keystring, newname);
|
|
|
|
DPrintf ("Music %s set to:\n%s\n", keystring.GetChars(), newname);
|
|
|
|
}
|
|
|
|
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
2006-02-24 04:48:15 +00:00
|
|
|
static int PatchText (int oldSize)
|
|
|
|
{
|
|
|
|
int newSize;
|
|
|
|
char *oldStr;
|
|
|
|
char *newStr;
|
|
|
|
char *temp;
|
2006-09-14 00:02:31 +00:00
|
|
|
INTBOOL good;
|
2006-02-24 04:48:15 +00:00
|
|
|
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);
|
|
|
|
}
|
2014-09-13 09:54:19 +00:00
|
|
|
for (unsigned ii = 0; ii < OrgSprNames.Size(); ii++)
|
|
|
|
{
|
|
|
|
if (!stricmp(OrgSprNames[ii].c, oldStr))
|
|
|
|
{
|
|
|
|
strcpy(OrgSprNames[ii].c, newStr);
|
|
|
|
}
|
|
|
|
}
|
2006-02-24 04:48:15 +00:00
|
|
|
// 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)
|
|
|
|
{
|
2010-07-23 21:19:59 +00:00
|
|
|
swapvalues (DehSpriteMappings[i-1], DehSpriteMappings[i]);
|
2006-02-24 04:48:15 +00:00
|
|
|
--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)
|
|
|
|
{
|
2010-07-23 21:19:59 +00:00
|
|
|
swapvalues (DehSpriteMappings[i+1], DehSpriteMappings[i]);
|
2006-02-24 04:48:15 +00:00
|
|
|
++i;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
goto donewithtext;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-02-09 09:15:11 +00:00
|
|
|
// Search through most other texts
|
|
|
|
const char *str;
|
|
|
|
do
|
|
|
|
{
|
|
|
|
str = EnglishStrings->MatchString(oldStr);
|
2009-03-28 11:49:44 +00:00
|
|
|
if (str != NULL)
|
|
|
|
{
|
2015-02-09 09:15:11 +00:00
|
|
|
GStrings.SetString(str, newStr);
|
|
|
|
EnglishStrings->SetString(str, "~~"); // set to something invalid so that it won't get found again by the next iteration or by another replacement later
|
2009-03-28 11:49:44 +00:00
|
|
|
good = true;
|
|
|
|
}
|
2015-02-09 09:15:11 +00:00
|
|
|
}
|
|
|
|
while (str != NULL); // repeat search until the text can no longer be found
|
2009-03-28 11:49:44 +00:00
|
|
|
|
2015-02-09 09:15:11 +00:00
|
|
|
if (!good)
|
|
|
|
{
|
|
|
|
DPrintf (" (Unmatched)\n");
|
2009-03-28 11:49:44 +00:00
|
|
|
}
|
|
|
|
|
2006-02-24 04:48:15 +00:00
|
|
|
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();
|
2014-12-07 08:30:16 +00:00
|
|
|
// Account for a discrepancy between Boom's and ZDoom's name for the red skull key pickup message
|
2014-12-07 13:46:53 +00:00
|
|
|
const char *ll = Line1;
|
|
|
|
if (!stricmp(ll, "GOTREDSKULL")) ll = "GOTREDSKUL";
|
|
|
|
GStrings.SetString (ll, 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;
|
2009-12-25 00:34:34 +00:00
|
|
|
int savedversion, savepversion, savepatchsize;
|
|
|
|
char *savepatchfile, *savepatchpt, *savepatchname;
|
2006-02-24 04:48:15 +00:00
|
|
|
|
|
|
|
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;
|
2009-12-20 19:14:10 +00:00
|
|
|
savepatchsize = PatchSize;
|
2006-02-24 04:48:15 +00:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2009-11-12 02:47:28 +00:00
|
|
|
D_LoadDehFile(path);
|
2006-02-24 04:48:15 +00:00
|
|
|
|
|
|
|
if (data != path)
|
|
|
|
{
|
|
|
|
delete[] path;
|
|
|
|
}
|
|
|
|
|
|
|
|
DPrintf ("Done with include\n");
|
|
|
|
PatchName = savepatchname;
|
|
|
|
PatchFile = savepatchfile;
|
|
|
|
PatchPt = savepatchpt;
|
2009-12-20 19:14:10 +00:00
|
|
|
PatchSize = savepatchsize;
|
2006-02-24 04:48:15 +00:00
|
|
|
dversion = savedversion;
|
|
|
|
pversion = savepversion;
|
|
|
|
}
|
|
|
|
|
|
|
|
including = false;
|
|
|
|
includenotext = false;
|
|
|
|
return GetLine();
|
|
|
|
}
|
|
|
|
|
2013-07-30 07:32:33 +00:00
|
|
|
CVAR(Int, dehload, 0, CVAR_ARCHIVE) // Autoloading of .DEH lumps is disabled by default.
|
|
|
|
|
|
|
|
// checks if lump is a .deh or .bex file. Only lumps in the root directory are considered valid.
|
|
|
|
static bool isDehFile(int lumpnum)
|
|
|
|
{
|
|
|
|
const char* const fullName = Wads.GetLumpFullName(lumpnum);
|
|
|
|
const char* const extension = strrchr(fullName, '.');
|
|
|
|
|
|
|
|
return NULL != extension && strchr(fullName, '/') == NULL
|
|
|
|
&& (0 == stricmp(extension, ".deh") || 0 == stricmp(extension, ".bex"));
|
|
|
|
}
|
|
|
|
|
2009-11-12 02:47:28 +00:00
|
|
|
int D_LoadDehLumps()
|
2006-02-24 04:48:15 +00:00
|
|
|
{
|
2009-11-12 02:47:28 +00:00
|
|
|
int lastlump = 0, lumpnum, count = 0;
|
2006-02-24 04:48:15 +00:00
|
|
|
|
2009-11-12 02:47:28 +00:00
|
|
|
while ((lumpnum = Wads.FindLump("DEHACKED", &lastlump)) >= 0)
|
2008-08-26 18:32:17 +00:00
|
|
|
{
|
2009-11-12 02:47:28 +00:00
|
|
|
count += D_LoadDehLump(lumpnum);
|
2008-08-26 18:32:17 +00:00
|
|
|
}
|
2013-06-23 16:34:57 +00:00
|
|
|
|
2013-07-30 07:32:33 +00:00
|
|
|
if (0 == PatchSize && dehload > 0)
|
2013-06-23 16:34:57 +00:00
|
|
|
{
|
|
|
|
// No DEH/BEX patch is loaded yet, try to find lump(s) with specific extensions
|
|
|
|
|
2013-07-30 07:32:33 +00:00
|
|
|
if (dehload == 1) // load all .DEH lumps that are found.
|
2013-06-23 16:34:57 +00:00
|
|
|
{
|
2013-07-30 07:32:33 +00:00
|
|
|
for (lumpnum = 0, lastlump = Wads.GetNumLumps(); lumpnum < lastlump; ++lumpnum)
|
|
|
|
{
|
|
|
|
if (isDehFile(lumpnum))
|
|
|
|
{
|
|
|
|
count += D_LoadDehLump(lumpnum);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else // only load the last .DEH lump that is found.
|
|
|
|
{
|
|
|
|
for (lumpnum = Wads.GetNumLumps()-1; lumpnum >=0; --lumpnum)
|
2013-06-23 16:34:57 +00:00
|
|
|
{
|
2013-07-30 07:32:33 +00:00
|
|
|
if (isDehFile(lumpnum))
|
|
|
|
{
|
|
|
|
count += D_LoadDehLump(lumpnum);
|
|
|
|
break;
|
|
|
|
}
|
2013-06-23 16:34:57 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2009-11-12 02:47:28 +00:00
|
|
|
return count;
|
|
|
|
}
|
2006-02-24 04:48:15 +00:00
|
|
|
|
2009-11-12 02:47:28 +00:00
|
|
|
bool D_LoadDehLump(int lumpnum)
|
|
|
|
{
|
2009-12-20 19:14:10 +00:00
|
|
|
PatchSize = Wads.LumpLength(lumpnum);
|
2006-02-24 04:48:15 +00:00
|
|
|
|
2009-11-12 02:47:28 +00:00
|
|
|
PatchName = copystring(Wads.GetLumpFullPath(lumpnum));
|
2009-12-20 19:14:10 +00:00
|
|
|
PatchFile = new char[PatchSize + 1];
|
2009-11-12 02:47:28 +00:00
|
|
|
Wads.ReadLump(lumpnum, PatchFile);
|
2009-12-20 19:14:10 +00:00
|
|
|
PatchFile[PatchSize] = '\0'; // terminate with a '\0' character
|
2009-11-12 02:47:28 +00:00
|
|
|
return DoDehPatch();
|
|
|
|
}
|
2006-02-24 04:48:15 +00:00
|
|
|
|
2009-11-12 02:47:28 +00:00
|
|
|
bool D_LoadDehFile(const char *patchfile)
|
|
|
|
{
|
|
|
|
FILE *deh;
|
2006-02-24 04:48:15 +00:00
|
|
|
|
2009-11-12 02:47:28 +00:00
|
|
|
deh = fopen(patchfile, "rb");
|
|
|
|
if (deh != NULL)
|
|
|
|
{
|
2009-12-20 19:14:10 +00:00
|
|
|
PatchSize = Q_filelength(deh);
|
2006-02-24 04:48:15 +00:00
|
|
|
|
2009-11-12 02:47:28 +00:00
|
|
|
PatchName = copystring(patchfile);
|
2009-12-20 19:14:10 +00:00
|
|
|
PatchFile = new char[PatchSize + 1];
|
|
|
|
fread(PatchFile, 1, PatchSize, deh);
|
2009-11-12 02:47:28 +00:00
|
|
|
fclose(deh);
|
2009-12-20 19:14:10 +00:00
|
|
|
PatchFile[PatchSize] = '\0'; // terminate with a '\0' character
|
2009-11-12 02:47:28 +00:00
|
|
|
return DoDehPatch();
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
// Couldn't find it in the filesystem; try from a lump instead.
|
|
|
|
int lumpnum = Wads.CheckNumForFullName(patchfile, true);
|
|
|
|
if (lumpnum < 0)
|
2006-02-24 04:48:15 +00:00
|
|
|
{
|
2009-11-12 02:47:28 +00:00
|
|
|
// Compatibility fallback. It's just here because
|
|
|
|
// some WAD may need it. Should be deleted if it can
|
|
|
|
// be confirmed that nothing uses this case.
|
|
|
|
FString filebase(ExtractFileBase(patchfile));
|
|
|
|
lumpnum = Wads.CheckNumForName(filebase);
|
2006-02-24 04:48:15 +00:00
|
|
|
}
|
2009-11-12 02:47:28 +00:00
|
|
|
if (lumpnum >= 0)
|
2006-02-24 04:48:15 +00:00
|
|
|
{
|
2009-11-12 02:47:28 +00:00
|
|
|
return D_LoadDehLump(lumpnum);
|
2006-02-24 04:48:15 +00:00
|
|
|
}
|
|
|
|
}
|
2009-11-12 02:47:28 +00:00
|
|
|
Printf ("Could not open DeHackEd patch \"%s\"\n", patchfile);
|
|
|
|
return false;
|
|
|
|
}
|
2006-02-24 04:48:15 +00:00
|
|
|
|
2009-11-12 02:47:28 +00:00
|
|
|
static bool DoDehPatch()
|
|
|
|
{
|
|
|
|
Printf("Adding dehacked patch %s\n", PatchName);
|
|
|
|
|
|
|
|
int cont;
|
2006-02-24 04:48:15 +00:00
|
|
|
|
|
|
|
dversion = pversion = -1;
|
|
|
|
cont = 0;
|
|
|
|
if (0 == strncmp (PatchFile, "Patch File for DeHackEd v", 25))
|
|
|
|
{
|
|
|
|
if (PatchFile[25] < '3')
|
|
|
|
{
|
2009-11-12 02:47:28 +00:00
|
|
|
delete[] PatchName;
|
2006-02-24 04:48:15 +00:00
|
|
|
delete[] PatchFile;
|
2009-11-12 02:47:28 +00:00
|
|
|
Printf (PRINT_BOLD, "\"%s\" is an old and unsupported DeHackEd patch\n", PatchFile);
|
|
|
|
return false;
|
2006-02-24 04:48:15 +00:00
|
|
|
}
|
2015-02-07 14:27:31 +00:00
|
|
|
// fix for broken WolfenDoom patches which contain \0 characters in some places.
|
|
|
|
for (int i = 0; i < PatchSize; i++)
|
|
|
|
{
|
|
|
|
if (PatchFile[i] == 0) PatchFile[i] = ' ';
|
|
|
|
}
|
|
|
|
|
2006-02-24 04:48:15 +00:00
|
|
|
PatchPt = strchr (PatchFile, '\n');
|
|
|
|
while ((cont = GetLine()) == 1)
|
|
|
|
{
|
|
|
|
CHECKKEY ("Doom version", dversion)
|
|
|
|
else CHECKKEY ("Patch format", pversion)
|
|
|
|
}
|
|
|
|
if (!cont || dversion == -1 || pversion == -1)
|
|
|
|
{
|
2009-11-12 02:47:28 +00:00
|
|
|
delete[] PatchName;
|
2006-02-24 04:48:15 +00:00
|
|
|
delete[] PatchFile;
|
2009-11-12 02:47:28 +00:00
|
|
|
Printf (PRINT_BOLD, "\"%s\" is not a DeHackEd patch file\n", PatchFile);
|
|
|
|
return false;
|
2006-02-24 04:48:15 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
DPrintf ("Patch does not have DeHackEd signature. Assuming .bex\n");
|
|
|
|
dversion = 19;
|
|
|
|
pversion = 6;
|
|
|
|
PatchPt = PatchFile;
|
|
|
|
while ((cont = GetLine()) == 1)
|
2009-11-12 02:47:28 +00:00
|
|
|
{}
|
2006-02-24 04:48:15 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
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");
|
|
|
|
UnloadDehSupp ();
|
2009-11-12 02:47:28 +00:00
|
|
|
delete[] PatchName;
|
|
|
|
delete[] PatchFile;
|
|
|
|
return false;
|
2006-02-24 04:48:15 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
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 ();
|
2009-11-12 02:47:28 +00:00
|
|
|
delete[] PatchName;
|
2006-02-24 04:48:15 +00:00
|
|
|
delete[] PatchFile;
|
|
|
|
Printf ("Patch installed\n");
|
2009-11-12 02:47:28 +00:00
|
|
|
return true;
|
2006-02-24 04:48:15 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
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 void UnloadDehSupp ()
|
|
|
|
{
|
|
|
|
if (--DehUseCount <= 0)
|
|
|
|
{
|
2009-09-14 22:12:31 +00:00
|
|
|
// Handle MBF params here, before the required arrays are cleared
|
|
|
|
for (unsigned int i=0; i < MBFParamStates.Size(); i++)
|
|
|
|
{
|
2009-09-15 23:31:28 +00:00
|
|
|
SetDehParams(MBFParamStates[i].state, MBFParamStates[i].pointer);
|
2009-09-14 22:12:31 +00:00
|
|
|
}
|
|
|
|
MBFParamStates.Clear();
|
|
|
|
MBFParamStates.ShrinkToFit();
|
|
|
|
MBFCodePointers.Clear();
|
|
|
|
MBFCodePointers.ShrinkToFit();
|
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;
|
2009-03-28 11:49:44 +00:00
|
|
|
Actions.Clear();
|
|
|
|
Actions.ShrinkToFit();
|
|
|
|
OrgHeights.Clear();
|
|
|
|
OrgHeights.ShrinkToFit();
|
|
|
|
CodePConv.Clear();
|
|
|
|
CodePConv.ShrinkToFit();
|
|
|
|
OrgSprNames.Clear();
|
|
|
|
OrgSprNames.ShrinkToFit();
|
|
|
|
SoundMap.Clear();
|
|
|
|
SoundMap.ShrinkToFit();
|
|
|
|
InfoNames.Clear();
|
|
|
|
InfoNames.ShrinkToFit();
|
|
|
|
BitNames.Clear();
|
|
|
|
BitNames.ShrinkToFit();
|
|
|
|
StyleNames.Clear();
|
|
|
|
StyleNames.ShrinkToFit();
|
|
|
|
AmmoNames.Clear();
|
|
|
|
AmmoNames.ShrinkToFit();
|
|
|
|
|
2006-02-24 04:48:15 +00:00
|
|
|
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 ()
|
|
|
|
{
|
2009-03-28 11:49:44 +00:00
|
|
|
try
|
2008-03-23 05:24:40 +00:00
|
|
|
{
|
2009-03-28 11:49:44 +00:00
|
|
|
// Make sure we only get the DEHSUPP lump from zdoom.pk3
|
|
|
|
// User modifications are not supported!
|
2009-08-02 18:23:05 +00:00
|
|
|
int lump = Wads.CheckNumForName("DEHSUPP");
|
2008-03-23 05:24:40 +00:00
|
|
|
|
2009-03-28 11:49:44 +00:00
|
|
|
if (lump == -1)
|
2006-02-24 04:48:15 +00:00
|
|
|
{
|
2009-03-28 11:49:44 +00:00
|
|
|
return false;
|
2006-02-24 04:48:15 +00:00
|
|
|
}
|
2008-03-23 05:24:40 +00:00
|
|
|
|
2009-08-02 18:23:05 +00:00
|
|
|
if (Wads.GetLumpFile(lump) > 0)
|
|
|
|
{
|
|
|
|
Printf("Warning: DEHSUPP no longer supported. DEHACKED patch disabled.\n");
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
bool gotnames = false;
|
|
|
|
int i;
|
|
|
|
|
|
|
|
|
2009-03-28 11:49:44 +00:00
|
|
|
if (++DehUseCount > 1)
|
2006-02-24 04:48:15 +00:00
|
|
|
{
|
2009-03-28 11:49:44 +00:00
|
|
|
return true;
|
2006-02-24 04:48:15 +00:00
|
|
|
}
|
2009-03-28 11:49:44 +00:00
|
|
|
|
|
|
|
if (EnglishStrings == NULL)
|
2006-02-24 04:48:15 +00:00
|
|
|
{
|
2009-03-28 11:49:44 +00:00
|
|
|
EnglishStrings = new FStringTable;
|
|
|
|
EnglishStrings->LoadStrings (true);
|
2006-02-24 04:48:15 +00:00
|
|
|
}
|
2009-03-28 11:49:44 +00:00
|
|
|
|
|
|
|
if (UnchangedSpriteNames == NULL)
|
2006-02-24 04:48:15 +00:00
|
|
|
{
|
2009-03-28 11:49:44 +00:00
|
|
|
UnchangedSpriteNames = new char[sprites.Size()*4];
|
|
|
|
NumUnchangedSprites = sprites.Size();
|
|
|
|
for (i = 0; i < NumUnchangedSprites; ++i)
|
2006-02-24 04:48:15 +00:00
|
|
|
{
|
2009-03-28 11:49:44 +00:00
|
|
|
memcpy (UnchangedSpriteNames+i*4, &sprites[i].name, 4);
|
2006-02-24 04:48:15 +00:00
|
|
|
}
|
2008-03-23 05:24:40 +00:00
|
|
|
}
|
|
|
|
|
2009-03-28 11:49:44 +00:00
|
|
|
FScanner sc;
|
|
|
|
|
|
|
|
sc.OpenLumpNum(lump);
|
|
|
|
sc.SetCMode(true);
|
|
|
|
|
|
|
|
while (sc.GetString())
|
|
|
|
{
|
|
|
|
if (sc.Compare("ActionList"))
|
2008-03-22 21:07:31 +00:00
|
|
|
{
|
2009-03-28 11:49:44 +00:00
|
|
|
sc.MustGetStringName("{");
|
|
|
|
while (!sc.CheckString("}"))
|
|
|
|
{
|
|
|
|
sc.MustGetString();
|
|
|
|
if (sc.Compare("NULL"))
|
|
|
|
{
|
|
|
|
Actions.Push(NULL);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
// all relevant code pointers are either defined in AInventory
|
|
|
|
// or AActor so this will find all of them.
|
|
|
|
FString name = "A_";
|
|
|
|
name << sc.String;
|
|
|
|
PSymbol *sym = RUNTIME_CLASS(AInventory)->Symbols.FindSymbol(name, true);
|
|
|
|
if (sym == NULL || sym->SymbolType != SYM_ActionFunction)
|
|
|
|
{
|
|
|
|
sc.ScriptError("Unknown code pointer '%s'", sc.String);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
FString &args = static_cast<PSymbolActionFunction*>(sym)->Arguments;
|
|
|
|
if (args.Len()!=0 && (args[0]<'a' || args[0]>'z'))
|
|
|
|
{
|
|
|
|
sc.ScriptError("Incompatible code pointer '%s'", sc.String);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
Actions.Push(sym);
|
|
|
|
}
|
|
|
|
if (sc.CheckString("}")) break;
|
|
|
|
sc.MustGetStringName(",");
|
|
|
|
}
|
2008-03-22 21:07:31 +00:00
|
|
|
}
|
2009-03-28 11:49:44 +00:00
|
|
|
else if (sc.Compare("OrgHeights"))
|
2006-02-24 04:48:15 +00:00
|
|
|
{
|
2009-03-28 11:49:44 +00:00
|
|
|
sc.MustGetStringName("{");
|
|
|
|
while (!sc.CheckString("}"))
|
|
|
|
{
|
|
|
|
sc.MustGetNumber();
|
|
|
|
OrgHeights.Push(sc.Number);
|
|
|
|
if (sc.CheckString("}")) break;
|
|
|
|
sc.MustGetStringName(",");
|
|
|
|
}
|
2006-02-24 04:48:15 +00:00
|
|
|
}
|
2009-03-28 11:49:44 +00:00
|
|
|
else if (sc.Compare("CodePConv"))
|
2006-02-24 04:48:15 +00:00
|
|
|
{
|
2009-03-28 11:49:44 +00:00
|
|
|
sc.MustGetStringName("{");
|
|
|
|
while (!sc.CheckString("}"))
|
2006-02-24 04:48:15 +00:00
|
|
|
{
|
2009-03-28 11:49:44 +00:00
|
|
|
sc.MustGetNumber();
|
|
|
|
CodePConv.Push(sc.Number);
|
|
|
|
if (sc.CheckString("}")) break;
|
|
|
|
sc.MustGetStringName(",");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else if (sc.Compare("OrgSprNames"))
|
|
|
|
{
|
|
|
|
sc.MustGetStringName("{");
|
|
|
|
while (!sc.CheckString("}"))
|
|
|
|
{
|
|
|
|
sc.MustGetString();
|
|
|
|
DEHSprName s;
|
2009-07-25 15:00:45 +00:00
|
|
|
// initialize with zeroes
|
|
|
|
memset(&s, 0, sizeof(s));
|
2009-03-28 11:49:44 +00:00
|
|
|
if (strlen(sc.String) ==4)
|
|
|
|
{
|
|
|
|
s.c[0] = sc.String[0];
|
|
|
|
s.c[1] = sc.String[1];
|
|
|
|
s.c[2] = sc.String[2];
|
|
|
|
s.c[3] = sc.String[3];
|
|
|
|
s.c[4] = 0;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
sc.ScriptError("Invalid sprite name '%s' (must be 4 characters)", sc.String);
|
|
|
|
}
|
|
|
|
OrgSprNames.Push(s);
|
|
|
|
if (sc.CheckString("}")) break;
|
|
|
|
sc.MustGetStringName(",");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else if (sc.Compare("StateMap"))
|
|
|
|
{
|
|
|
|
bool addit = StateMap.Size() == 0;
|
|
|
|
|
|
|
|
sc.MustGetStringName("{");
|
|
|
|
while (!sc.CheckString("}"))
|
|
|
|
{
|
|
|
|
StateMapper s;
|
|
|
|
sc.MustGetString();
|
|
|
|
|
|
|
|
const PClass *type = PClass::FindClass (sc.String);
|
2006-02-24 04:48:15 +00:00
|
|
|
if (type == NULL)
|
|
|
|
{
|
2009-03-28 11:49:44 +00:00
|
|
|
sc.ScriptError ("Can't find type %s", sc.String);
|
2006-02-24 04:48:15 +00:00
|
|
|
}
|
|
|
|
else if (type->ActorInfo == NULL)
|
|
|
|
{
|
2009-03-28 11:49:44 +00:00
|
|
|
sc.ScriptError ("%s has no ActorInfo", sc.String);
|
2006-02-24 04:48:15 +00:00
|
|
|
}
|
2009-03-28 11:49:44 +00:00
|
|
|
|
|
|
|
sc.MustGetStringName(",");
|
|
|
|
sc.MustGetString();
|
|
|
|
s.State = type->ActorInfo->FindState(sc.String);
|
|
|
|
if (s.State == NULL)
|
2006-02-24 04:48:15 +00:00
|
|
|
{
|
2009-03-28 11:49:44 +00:00
|
|
|
sc.ScriptError("Invalid state '%s' in '%s'", sc.String, type->TypeName.GetChars());
|
|
|
|
}
|
2006-02-24 04:48:15 +00:00
|
|
|
|
2009-03-28 11:49:44 +00:00
|
|
|
sc.MustGetStringName(",");
|
|
|
|
sc.MustGetNumber();
|
|
|
|
if (s.State == NULL || s.State + sc.Number > type->ActorInfo->OwnedStates + type->ActorInfo->NumOwnedStates)
|
|
|
|
{
|
|
|
|
sc.ScriptError("Invalid state range in '%s'", type->TypeName.GetChars());
|
|
|
|
}
|
|
|
|
AActor *def = GetDefaultByType(type);
|
|
|
|
|
|
|
|
s.StateSpan = sc.Number;
|
|
|
|
s.Owner = type;
|
|
|
|
s.OwnerIsPickup = def != NULL && (def->flags & MF_SPECIAL) != 0;
|
|
|
|
if (addit) StateMap.Push(s);
|
|
|
|
|
|
|
|
if (sc.CheckString("}")) break;
|
|
|
|
sc.MustGetStringName(",");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else if (sc.Compare("SoundMap"))
|
|
|
|
{
|
|
|
|
sc.MustGetStringName("{");
|
|
|
|
while (!sc.CheckString("}"))
|
|
|
|
{
|
|
|
|
sc.MustGetString();
|
|
|
|
SoundMap.Push(S_FindSound(sc.String));
|
|
|
|
if (sc.CheckString("}")) break;
|
|
|
|
sc.MustGetStringName(",");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else if (sc.Compare("InfoNames"))
|
|
|
|
{
|
|
|
|
sc.MustGetStringName("{");
|
|
|
|
while (!sc.CheckString("}"))
|
|
|
|
{
|
|
|
|
sc.MustGetString();
|
|
|
|
const PClass *cls = PClass::FindClass(sc.String);
|
|
|
|
if (cls == NULL)
|
|
|
|
{
|
|
|
|
sc.ScriptError("Unknown actor type '%s'", sc.String);
|
|
|
|
}
|
|
|
|
InfoNames.Push(cls);
|
|
|
|
if (sc.CheckString("}")) break;
|
|
|
|
sc.MustGetStringName(",");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else if (sc.Compare("ThingBits"))
|
|
|
|
{
|
|
|
|
sc.MustGetStringName("{");
|
|
|
|
while (!sc.CheckString("}"))
|
|
|
|
{
|
|
|
|
BitName bit;
|
|
|
|
sc.MustGetNumber();
|
|
|
|
if (sc.Number < 0 || sc.Number > 31)
|
|
|
|
{
|
|
|
|
sc.ScriptError("Invalid bit value %d", sc.Number);
|
|
|
|
}
|
|
|
|
bit.Bit = sc.Number;
|
|
|
|
sc.MustGetStringName(",");
|
|
|
|
sc.MustGetNumber();
|
|
|
|
if (sc.Number < 0 || sc.Number > 2)
|
|
|
|
{
|
|
|
|
sc.ScriptError("Invalid flag word %d", sc.Number);
|
|
|
|
}
|
|
|
|
bit.WhichFlags = sc.Number;
|
|
|
|
sc.MustGetStringName(",");
|
|
|
|
sc.MustGetString();
|
|
|
|
strncpy(bit.Name, sc.String, 19);
|
|
|
|
bit.Name[19]=0;
|
|
|
|
BitNames.Push(bit);
|
|
|
|
if (sc.CheckString("}")) break;
|
|
|
|
sc.MustGetStringName(",");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else if (sc.Compare("RenderStyles"))
|
|
|
|
{
|
|
|
|
sc.MustGetStringName("{");
|
|
|
|
while (!sc.CheckString("}"))
|
|
|
|
{
|
|
|
|
StyleName style;
|
|
|
|
sc.MustGetNumber();
|
|
|
|
style.Num = sc.Number;
|
|
|
|
sc.MustGetStringName(",");
|
|
|
|
sc.MustGetString();
|
|
|
|
strncpy(style.Name, sc.String, 19);
|
|
|
|
style.Name[19]=0;
|
|
|
|
StyleNames.Push(style);
|
|
|
|
if (sc.CheckString("}")) break;
|
|
|
|
sc.MustGetStringName(",");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else if (sc.Compare("AmmoNames"))
|
|
|
|
{
|
|
|
|
sc.MustGetStringName("{");
|
|
|
|
while (!sc.CheckString("}"))
|
|
|
|
{
|
|
|
|
sc.MustGetString();
|
|
|
|
if (sc.Compare("NULL"))
|
|
|
|
{
|
|
|
|
AmmoNames.Push(NULL);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
const PClass *cls = PClass::FindClass(sc.String);
|
|
|
|
if (cls == NULL || cls->ParentClass != RUNTIME_CLASS(AAmmo))
|
2006-02-24 04:48:15 +00:00
|
|
|
{
|
2009-03-28 11:49:44 +00:00
|
|
|
sc.ScriptError("Unknown ammo type '%s'", sc.String);
|
2006-02-24 04:48:15 +00:00
|
|
|
}
|
2009-03-28 11:49:44 +00:00
|
|
|
AmmoNames.Push(cls);
|
2006-02-24 04:48:15 +00:00
|
|
|
}
|
2009-03-28 11:49:44 +00:00
|
|
|
if (sc.CheckString("}")) break;
|
|
|
|
sc.MustGetStringName(",");
|
2006-02-24 04:48:15 +00:00
|
|
|
}
|
|
|
|
}
|
2009-03-28 11:49:44 +00:00
|
|
|
else if (sc.Compare("WeaponNames"))
|
2008-03-22 21:07:31 +00:00
|
|
|
{
|
2012-04-07 11:33:35 +00:00
|
|
|
WeaponNames.Clear(); // This won't be cleared by UnloadDEHSupp so we need to do it here explicitly
|
2009-03-28 11:49:44 +00:00
|
|
|
sc.MustGetStringName("{");
|
|
|
|
while (!sc.CheckString("}"))
|
|
|
|
{
|
|
|
|
sc.MustGetString();
|
|
|
|
const PClass *cls = PClass::FindClass(sc.String);
|
|
|
|
if (cls == NULL || !cls->IsDescendantOf(RUNTIME_CLASS(AWeapon)))
|
|
|
|
{
|
|
|
|
sc.ScriptError("Unknown weapon type '%s'", sc.String);
|
|
|
|
}
|
|
|
|
WeaponNames.Push(cls);
|
|
|
|
if (sc.CheckString("}")) break;
|
|
|
|
sc.MustGetStringName(",");
|
|
|
|
}
|
2008-03-22 21:07:31 +00:00
|
|
|
}
|
2009-09-14 22:12:31 +00:00
|
|
|
else if (sc.Compare("Aliases"))
|
|
|
|
{
|
|
|
|
sc.MustGetStringName("{");
|
|
|
|
while (!sc.CheckString("}"))
|
|
|
|
{
|
|
|
|
CodePointerAlias temp;
|
|
|
|
sc.MustGetString();
|
|
|
|
strncpy(temp.alias, sc.String, 19);
|
|
|
|
temp.alias[19]=0;
|
|
|
|
sc.MustGetStringName(",");
|
|
|
|
sc.MustGetString();
|
|
|
|
strncpy(temp.name, sc.String, 19);
|
|
|
|
temp.name[19]=0;
|
|
|
|
sc.MustGetStringName(",");
|
|
|
|
sc.MustGetNumber();
|
|
|
|
temp.params = sc.Number;
|
|
|
|
MBFCodePointers.Push(temp);
|
|
|
|
if (sc.CheckString("}")) break;
|
|
|
|
sc.MustGetStringName(",");
|
|
|
|
}
|
|
|
|
}
|
2009-03-28 11:49:44 +00:00
|
|
|
else
|
2008-03-22 21:07:31 +00:00
|
|
|
{
|
2009-03-28 11:49:44 +00:00
|
|
|
sc.ScriptError("Unknown section '%s'", sc.String);
|
2008-03-22 21:07:31 +00:00
|
|
|
}
|
2009-03-28 11:49:44 +00:00
|
|
|
|
|
|
|
sc.MustGetStringName(";");
|
2006-02-24 04:48:15 +00:00
|
|
|
}
|
2009-03-28 11:49:44 +00:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
catch(CRecoverableError &err)
|
|
|
|
{
|
|
|
|
// Don't abort if DEHSUPP loading fails.
|
|
|
|
// Just print the message and continue.
|
|
|
|
Printf("%s\n", err.GetMessage());
|
|
|
|
return false;
|
2006-02-24 04:48:15 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
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];
|
About a week's worth of changes here. As a heads-up, I wouldn't be
surprised if this doesn't build in Linux right now. The CMakeLists.txt
were checked with MinGW and NMake, but how they fair under Linux is an
unknown to me at this time.
- Converted most sprintf (and all wsprintf) calls to either mysnprintf or
FStrings, depending on the situation.
- Changed the strings in the wbstartstruct to be FStrings.
- Changed myvsnprintf() to output nothing if count is greater than INT_MAX.
This is so that I can use a series of mysnprintf() calls and advance the
pointer for each one. Once the pointer goes beyond the end of the buffer,
the count will go negative, but since it's an unsigned type it will be
seen as excessively huge instead. This should not be a problem, as there's
no reason for ZDoom to be using text buffers larger than 2 GB anywhere.
- Ripped out the disabled bit from FGameConfigFile::MigrateOldConfig().
- Changed CalcMapName() to return an FString instead of a pointer to a static
buffer.
- Changed startmap in d_main.cpp into an FString.
- Changed CheckWarpTransMap() to take an FString& as the first argument.
- Changed d_mapname in g_level.cpp into an FString.
- Changed DoSubstitution() in ct_chat.cpp to place the substitutions in an
FString.
- Fixed: The MAPINFO parser wrote into the string buffer to construct a map
name when given a Hexen map number. This was fine with the old scanner
code, but only a happy coincidence prevents it from crashing with the new
code
- Added the 'B' conversion specifier to StringFormat::VWorker() for printing
binary numbers.
- Added CMake support for building with MinGW, MSYS, and NMake. Linux support
is probably broken until I get around to booting into Linux again. Niceties
provided over the existing Makefiles they're replacing:
* All command-line builds can use the same build system, rather than having
a separate one for MinGW and another for Linux.
* Microsoft's NMake tool is supported as a target.
* Progress meters.
* Parallel makes work from a fresh checkout without needing to be primed
first with a single-threaded make.
* Porting to other architectures should be simplified, whenever that day
comes.
- Replaced the makewad tool with zipdir. This handles the dependency tracking
itself instead of generating an external makefile to do it, since I couldn't
figure out how to generate a makefile with an external tool and include it
with a CMake-generated makefile. Where makewad used a master list of files
to generate the package file, zipdir just zips the entire contents of one or
more directories.
- Added the gdtoa package from netlib's fp library so that ZDoom's printf-style
formatting can be entirely independant of the CRT.
SVN r1082 (trunk)
2008-07-23 04:57:26 +00:00
|
|
|
mysnprintf (typeNameBuilder, countof(typeNameBuilder), "DehackedPickup%d", touchedIndex);
|
2006-05-10 02:40:43 +00:00
|
|
|
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);
|
2013-07-31 10:15:33 +00:00
|
|
|
memcpy ((void *)defaults2, (void *)defaults1, sizeof(AActor));
|
2008-05-30 06:56:50 +00:00
|
|
|
|
2009-02-03 05:46:55 +00:00
|
|
|
// Make a copy of the replaced class's state labels
|
2008-09-24 23:22:55 +00:00
|
|
|
FStateDefinitions statedef;
|
|
|
|
statedef.MakeStateDefines(type);
|
|
|
|
|
2008-08-13 09:11:19 +00:00
|
|
|
if (!type->IsDescendantOf(RUNTIME_CLASS(AInventory)))
|
|
|
|
{
|
|
|
|
// If this is a hacked non-inventory item we must also copy AInventory's special states
|
2008-09-22 18:55:29 +00:00
|
|
|
statedef.AddStateDefines(RUNTIME_CLASS(AInventory)->ActorInfo->StateList);
|
2008-08-13 09:11:19 +00:00
|
|
|
}
|
2008-09-24 23:22:55 +00:00
|
|
|
statedef.InstallStates(subclass->ActorInfo, defaults2);
|
2006-02-24 04:48:15 +00:00
|
|
|
|
2008-05-30 06:56:50 +00:00
|
|
|
// Use the DECORATE replacement feature to redirect all spawns
|
|
|
|
// of the original class to the new one.
|
2009-02-03 05:46:55 +00:00
|
|
|
FActorInfo *old_replacement = type->ActorInfo->Replacement;
|
|
|
|
|
2008-05-30 06:56:50 +00:00
|
|
|
type->ActorInfo->Replacement = subclass->ActorInfo;
|
|
|
|
subclass->ActorInfo->Replacee = type->ActorInfo;
|
2009-02-03 05:46:55 +00:00
|
|
|
// If this actor was already replaced by another actor, copy that
|
|
|
|
// replacement over to this item.
|
|
|
|
if (old_replacement != NULL)
|
|
|
|
{
|
|
|
|
subclass->ActorInfo->Replacement = old_replacement;
|
|
|
|
}
|
2006-02-24 04:48:15 +00:00
|
|
|
|
2008-08-13 22:54:24 +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
|
|
|
|
2006-06-11 01:06:19 +00:00
|
|
|
// Now that all Dehacked patches have been processed, it's okay to free StateMap.
|
2009-03-28 11:49:44 +00:00
|
|
|
StateMap.Clear();
|
|
|
|
StateMap.ShrinkToFit();
|
2010-12-15 00:09:31 +00:00
|
|
|
TouchedActors.Clear();
|
|
|
|
TouchedActors.ShrinkToFit();
|
2012-04-07 11:33:35 +00:00
|
|
|
|
|
|
|
// Now it gets nasty: We have to fiddle around with the weapons' ammo use info to make Doom's original
|
|
|
|
// ammo consumption work as intended.
|
|
|
|
|
|
|
|
for(unsigned i = 0; i < WeaponNames.Size(); i++)
|
|
|
|
{
|
|
|
|
AWeapon *weap = (AWeapon*)GetDefaultByType(WeaponNames[i]);
|
|
|
|
bool found = false;
|
|
|
|
if (weap->flags6 & MF6_INTRYMOVE)
|
|
|
|
{
|
|
|
|
// Weapon sets an explicit amount of ammo to use so we won't need any special processing here
|
|
|
|
weap->flags6 &= ~MF6_INTRYMOVE;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
weap->WeaponFlags |= WIF_DEHAMMO;
|
|
|
|
weap->AmmoUse1 = 0;
|
|
|
|
// to allow proper checks in CheckAmmo we have to find the first attack pointer in the Fire sequence
|
|
|
|
// and set its default ammo use as the weapon's AmmoUse1.
|
|
|
|
|
|
|
|
TMap<FState*, bool> StateVisited;
|
|
|
|
|
|
|
|
FState *state = WeaponNames[i]->ActorInfo->FindState(NAME_Fire);
|
|
|
|
while (state != NULL)
|
|
|
|
{
|
|
|
|
bool *check = StateVisited.CheckKey(state);
|
|
|
|
if (check != NULL && *check)
|
|
|
|
{
|
|
|
|
break; // State has already been checked so we reached a loop
|
|
|
|
}
|
|
|
|
StateVisited[state] = true;
|
|
|
|
for(unsigned j = 0; AmmoPerAttacks[j].func != NULL; j++)
|
|
|
|
{
|
|
|
|
if (state->ActionFunc == AmmoPerAttacks[j].func)
|
|
|
|
{
|
|
|
|
found = true;
|
|
|
|
int use = AmmoPerAttacks[j].ammocount;
|
|
|
|
if (use < 0) use = deh.BFGCells;
|
|
|
|
weap->AmmoUse1 = use;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (found) break;
|
|
|
|
state = state->GetNextState();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
WeaponNames.Clear();
|
|
|
|
WeaponNames.ShrinkToFit();
|
2006-02-24 04:48:15 +00:00
|
|
|
}
|
|
|
|
|
2008-08-07 08:18:48 +00:00
|
|
|
void ModifyDropAmount(AInventory *inv, int dropamount);
|
|
|
|
|
2008-09-13 22:08:41 +00:00
|
|
|
bool ADehackedPickup::TryPickup (AActor *&toucher)
|
2006-02-24 04:48:15 +00:00
|
|
|
{
|
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)
|
|
|
|
{
|
2010-03-07 11:13:57 +00:00
|
|
|
// The internally spawned item should never count towards statistics.
|
2010-09-19 00:06:45 +00:00
|
|
|
RealPickup->ClearCounters();
|
2006-02-24 04:48:15 +00:00
|
|
|
if (!(flags & MF_DROPPED))
|
|
|
|
{
|
|
|
|
RealPickup->flags &= ~MF_DROPPED;
|
|
|
|
}
|
2008-05-30 06:56:50 +00:00
|
|
|
// If this item has been dropped by a monster the
|
2008-08-07 08:18:48 +00:00
|
|
|
// amount of ammo this gives must be adjusted.
|
2008-05-30 06:56:50 +00:00
|
|
|
if (droppedbymonster)
|
|
|
|
{
|
2008-08-07 08:18:48 +00:00
|
|
|
ModifyDropAmount(RealPickup, 0);
|
2008-05-30 06:56:50 +00:00
|
|
|
}
|
2008-09-13 22:08:41 +00:00
|
|
|
if (!RealPickup->CallTryPickup (toucher))
|
2006-02-24 04:48:15 +00:00
|
|
|
{
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2008-05-30 06:56:50 +00:00
|
|
|
void ADehackedPickup::Serialize(FArchive &arc)
|
|
|
|
{
|
|
|
|
Super::Serialize(arc);
|
|
|
|
arc << droppedbymonster;
|
|
|
|
}
|