- split Damage into two variables: DamageVal for the old constant and DamageFunc for the DECORATE function.

The way this was done was a major headache inducer, requiring reconstruction of the function each time the value was changed and in general made actor damage a major hassle.
There was a DECORATE wrapper to mimic the original behavior but this looked quite broken because it completely ignored the different semantics of both damage calculation types.
It also made it impossible to determine if damage was a function or a value.
This accessor has been reverted to what it should be, only returning the constant, which now is -1 for a damage function. I am sorry if this may break the odd mod out but a quick look over some DECORATE-heavy stuff showed that this was never combined in any of them so that accessing 'damage' in DECORATE code depended on an actual damage function.

To get proper damage, a future commit will add a DECORATE function which calls AActor::GetMissileDamage.
This commit is contained in:
Christoph Oelckers 2016-09-19 03:36:51 +02:00
parent 3db7d9ad84
commit f1ba19073f
16 changed files with 81 additions and 207 deletions

View File

@ -111,6 +111,8 @@ struct FPortalGroupArray;
// Any questions?
//
// --- mobj.flags ---
enum ActorFlag
{
@ -1014,7 +1016,9 @@ public:
SDWORD tics; // state tic counter
FState *state;
VMFunction *Damage; // For missiles and monster railgun
//VMFunction *Damage; // For missiles and monster railgun
int DamageVal;
VMFunction *DamageFunc;
int projectileKickback;
ActorFlags flags;
ActorFlags2 flags2; // Heretic flags
@ -1201,6 +1205,23 @@ public:
FState *GetRaiseState();
void Revive();
void SetDamage(int dmg)
{
DamageVal = dmg;
DamageFunc = nullptr;
}
bool IsZeroDamage() const
{
return DamageVal == 0 && DamageFunc == nullptr;
}
void RestoreDamage()
{
DamageVal = GetDefault()->DamageVal;
DamageFunc = GetDefault()->DamageFunc;
}
FState *FindState (FName label) const
{
return GetClass()->FindState(1, &label);

View File

@ -915,7 +915,7 @@ static int PatchThing (int thingy)
}
else if (linelen == 14 && stricmp (Line1, "Missile damage") == 0)
{
info->Damage = CreateDamageFunction(val);
info->SetDamage(val);
}
else if (linelen == 5)
{

View File

@ -47,7 +47,7 @@ static void BrainishExplosion (const DVector3 &pos)
boom->SetState (state);
}
boom->effects = 0;
boom->Damage = NULL; // disables collision detection which is not wanted here
boom->SetDamage(0); // disables collision detection which is not wanted here
boom->tics -= pr_brainscream() & 7;
if (boom->tics < 1)
boom->tics = 1;

View File

@ -115,7 +115,7 @@ DEFINE_ACTION_FUNCTION(AActor, A_LichAttack)
fire->target = baseFire->target;
fire->Angles.Yaw = baseFire->Angles.Yaw;
fire->Vel = baseFire->Vel;
fire->Damage = NULL;
fire->SetDamage(0);
fire->health = (i+1) * 2;
P_CheckMissileSpawn (fire, self->radius);
}
@ -205,7 +205,7 @@ DEFINE_ACTION_FUNCTION(AActor, A_LichFireGrow)
self->AddZ(9.);
if (self->health == 0)
{
self->Damage = self->GetDefault()->Damage;
self->RestoreDamage();
self->SetState (self->FindState("NoGrow"));
}
return 0;

View File

@ -340,7 +340,7 @@ DEFINE_ACTION_FUNCTION(AActor, A_LastZap)
{
mo->SetState (mo->FindState (NAME_Death));
mo->Vel.Z = 40;
mo->Damage = NULL;
mo->SetDamage(0);
}
return 0;
}

View File

@ -344,7 +344,7 @@ size_t PClassActor::PropagateMark()
// Mark damage function
if (Defaults != NULL)
{
GC::Mark(((AActor *)Defaults)->Damage);
GC::Mark(((AActor *)Defaults)->DamageFunc);
}
// marked += ActorInfo->NumOwnedStates * sizeof(FState);

View File

@ -3791,7 +3791,7 @@ void DLevelScript::DoSetActorProperty (AActor *actor, int property, int value)
break;
case APROP_Damage:
actor->Damage = CreateDamageFunction(value);
actor->SetDamage(value);
break;
case APROP_Alpha:

View File

@ -1320,7 +1320,7 @@ bool PIT_CheckThing(FMultiBlockThingsIterator &it, FMultiBlockThingsIterator::Ch
// [RH] What is the point of this check, again? In Hexen, it is unconditional,
// but here we only do it if the missile's damage is 0.
// MBF bouncer might have a non-0 damage value, but they must not deal damage on impact either.
if ((tm.thing->BounceFlags & BOUNCE_Actors) && (tm.thing->Damage == 0 || !(tm.thing->flags & MF_MISSILE)))
if ((tm.thing->BounceFlags & BOUNCE_Actors) && (tm.thing->IsZeroDamage() || !(tm.thing->flags & MF_MISSILE)))
{
return (tm.thing->target == thing || !(thing->flags & MF_SOLID));
}

View File

@ -139,7 +139,7 @@ IMPLEMENT_POINTY_CLASS (AActor)
DECLARE_POINTER (LastHeard)
DECLARE_POINTER (master)
DECLARE_POINTER (Poisoner)
DECLARE_POINTER (Damage)
DECLARE_POINTER (DamageFunc)
DECLARE_POINTER (alternative)
END_POINTERS
@ -149,73 +149,6 @@ AActor::~AActor ()
// Use Destroy() instead.
}
//==========================================================================
//
// CalcDamageValue
//
// Given a script function, returns an integer to represent it in a
// savegame. This encoding is compatible with previous incarnations
// where damage was an integer.
//
// 0 : use null function
// 0x40000000 : use default function
// anything else : use function that returns this number
//
//==========================================================================
static int CalcDamageValue(VMFunction *func)
{
if (func == NULL)
{
return 0;
}
VMScriptFunction *sfunc = dyn_cast<VMScriptFunction>(func);
if (sfunc == NULL)
{
return 0x40000000;
}
VMOP *op = sfunc->Code;
// If the function was created by CreateDamageFunction(), extract
// the value used to create it and return that. Otherwise, return
// indicating to use the default function.
if (op->op == OP_RETI && op->a == 0)
{
return op->i16;
}
if (op->op == OP_RET && op->a == 0 && op->b == (REGT_INT | REGT_KONST))
{
return sfunc->KonstD[op->c];
}
return 0x40000000;
}
//==========================================================================
//
// UncalcDamageValue
//
// Given a damage integer, returns a script function for it.
//
//==========================================================================
static VMFunction *UncalcDamageValue(int dmg, VMFunction *def)
{
if (dmg == 0)
{
return NULL;
}
if ((dmg & 0xC0000000) == 0x40000000)
{
return def;
}
// Does the default version return this? If so, use it. Otherwise,
// create a new function.
if (CalcDamageValue(def) == dmg)
{
return def;
}
return CreateDamageFunction(dmg);
}
//==========================================================================
//
// AActor :: Serialize
@ -263,18 +196,16 @@ void AActor::Serialize(FArchive &arc)
<< projectilepassheight
<< Vel
<< tics
<< state;
if (arc.IsStoring())
<< state
<< DamageVal;
if (DamageVal == 0x40000000 || DamageVal == -1)
{
int dmg;
dmg = CalcDamageValue(Damage);
arc << dmg;
DamageVal = -1;
DamageFunc = GetDefault()->DamageFunc;
}
else
{
int dmg;
arc << dmg;
Damage = UncalcDamageValue(dmg, GetDefault()->Damage);
DamageFunc = nullptr;
}
P_SerializeTerrain(arc, floorterrain);
arc << projectileKickback
@ -2975,8 +2906,21 @@ CCMD(utid)
int AActor::GetMissileDamage (int mask, int add)
{
if (Damage == NULL)
if (DamageVal >= 0)
{
if (mask == 0)
{
return add * DamageVal;
}
else
{
return ((pr_missiledamage() & mask) + add) * DamageVal;
}
}
if (DamageFunc == nullptr)
{
// This should never happen
assert(false && "No damage function found");
return 0;
}
VMFrameStack stack;
@ -2988,22 +2932,11 @@ int AActor::GetMissileDamage (int mask, int add)
results[0].IntAt(&amount);
results[1].IntAt(&calculated);
if (stack.Call(Damage, &param, 1, results, 2) < 1)
if (stack.Call(DamageFunc, &param, 1, results, 2) < 1)
{ // No results
return 0;
}
if (calculated)
{
return amount;
}
else if (mask == 0)
{
return add * amount;
}
else
{
return ((pr_missiledamage() & mask) + add) * amount;
}
return amount;
}
void AActor::Howl ()
@ -3695,7 +3628,7 @@ void AActor::Tick ()
// still have missiles that go straight up and down through actors without
// damaging anything.
// (for backwards compatibility this must check for lack of damage function, not for zero damage!)
if ((flags & MF_MISSILE) && Vel.X == 0 && Vel.Y == 0 && Damage != NULL)
if ((flags & MF_MISSILE) && Vel.X == 0 && Vel.Y == 0 && !IsZeroDamage())
{
Vel.X = MinVel;
}

View File

@ -496,8 +496,7 @@ static void ParseInsideDecoration (Baggage &bag, AActor *defaults,
else if (def == DEF_Projectile && sc.Compare ("Damage"))
{
sc.MustGetNumber ();
FxDamageValue *x = new FxDamageValue(new FxConstant(sc.Number, sc), false);
defaults->Damage = (VMFunction *)(uintptr_t)(ActorDamageFuncs.Push(x) + 1);
defaults->SetDamage(sc.Number);
}
else if (def == DEF_Projectile && sc.Compare ("DamageType"))
{

View File

@ -348,12 +348,12 @@ static void FinishThingdef()
continue;
}
if (def->Damage != NULL)
if (def->DamageFunc != nullptr)
{
FxDamageValue *dmg = (FxDamageValue *)ActorDamageFuncs[(uintptr_t)def->Damage - 1];
FxDamageValue *dmg = (FxDamageValue *)ActorDamageFuncs[(uintptr_t)def->DamageFunc - 1];
VMScriptFunction *sfunc;
sfunc = dmg->GetFunction();
if (sfunc == NULL)
if (sfunc == nullptr)
{
FCompileContext ctx(ti);
dmg = static_cast<FxDamageValue *>(dmg->Resolve(ctx));
@ -365,15 +365,15 @@ static void FinishThingdef()
dmg->Emit(&buildit);
sfunc = buildit.MakeFunction();
sfunc->NumArgs = 1;
sfunc->Proto = NULL; ///FIXME: Need a proper prototype here
sfunc->Proto = nullptr; ///FIXME: Need a proper prototype here
// Save this function in case this damage value was reused
// (which happens quite easily with inheritance).
dmg->SetFunction(sfunc);
}
}
def->Damage = sfunc;
def->DamageFunc = sfunc;
if (dump != NULL && sfunc != NULL)
if (dump != nullptr && sfunc != nullptr)
{
char label[64];
int labellen = mysnprintf(label, countof(label), "Function %s.Damage",

View File

@ -666,4 +666,5 @@ void InitThingdef()
symt.AddSymbol(new PField(NAME_Speed, TypeFloat64, VARF_Native, myoffsetof(AActor, Speed)));
symt.AddSymbol(new PField(NAME_Threshold, TypeSInt32, VARF_Native|VARF_ReadOnly, myoffsetof(AActor, threshold)));
symt.AddSymbol(new PField(NAME_DefThreshold, TypeSInt32, VARF_Native|VARF_ReadOnly, myoffsetof(AActor, DefThreshold)));
symt.AddSymbol(new PField(NAME_Damage, TypeSInt32, VARF_Native|VARF_ReadOnly, myoffsetof(AActor, DamageVal)));
}

View File

@ -854,20 +854,6 @@ public:
ExpEmit Emit(VMFunctionBuilder *build);
};
//==========================================================================
//
// FxDamage
//
//==========================================================================
class FxDamage : public FxExpression
{
public:
FxDamage(const FScriptPosition&);
FxExpression *Resolve(FCompileContext&);
ExpEmit Emit(VMFunctionBuilder *build);
};
//==========================================================================
//
// FxArrayElement
@ -1188,12 +1174,11 @@ public:
class FxDamageValue : public FxExpression
{
FxExpression *val;
bool Calculated;
VMScriptFunction *MyFunction;
public:
FxDamageValue(FxExpression *v, bool calc);
FxDamageValue(FxExpression *v);
~FxDamageValue();
FxExpression *Resolve(FCompileContext&);

View File

@ -3218,11 +3218,6 @@ FxExpression *FxIdentifier::Resolve(FCompileContext& ctx)
ScriptPosition.Message(MSG_ERROR, "Invalid member identifier '%s'\n", Identifier.GetChars());
}
}
// the damage property needs special handling
else if (Identifier == NAME_Damage)
{
newex = new FxDamage(ScriptPosition);
}
// now check the global identifiers.
else if ((sym = ctx.FindGlobal(Identifier)) != NULL)
{
@ -3316,65 +3311,6 @@ ExpEmit FxSelf::Emit(VMFunctionBuilder *build)
}
//==========================================================================
//
//
//
//==========================================================================
FxDamage::FxDamage(const FScriptPosition &pos)
: FxExpression(pos)
{
}
//==========================================================================
//
// FxDamage :: Resolve
//
//==========================================================================
FxExpression *FxDamage::Resolve(FCompileContext& ctx)
{
CHECKRESOLVED();
ValueType = TypeSInt32;
return this;
}
//==========================================================================
//
// FxDamage :: Emit
//
// Call this actor's damage function, if it has one
//
//==========================================================================
ExpEmit FxDamage::Emit(VMFunctionBuilder *build)
{
ExpEmit dmgval(build, REGT_INT);
// Get damage function
ExpEmit dmgfunc(build, REGT_POINTER);
build->Emit(OP_LO, dmgfunc.RegNum, 0/*self*/, build->GetConstantInt(myoffsetof(AActor, Damage)));
// If it's non-null...
build->Emit(OP_EQA_K, 1, dmgfunc.RegNum, build->GetConstantAddress(nullptr, ATAG_GENERIC));
size_t nulljump = build->Emit(OP_JMP, 0);
// ...call it
build->Emit(OP_PARAM, 0, REGT_POINTER, 0/*self*/);
build->Emit(OP_CALL, dmgfunc.RegNum, 1, 1);
build->Emit(OP_RESULT, 0, REGT_INT, dmgval.RegNum);
size_t notnulljump = build->Emit(OP_JMP, 0);
// Otherwise, use 0
build->BackpatchToHere(nulljump);
build->EmitLoadInt(dmgval.RegNum, 0);
build->BackpatchToHere(notnulljump);
return dmgval;
}
//==========================================================================
//
//
@ -5225,18 +5161,12 @@ ExpEmit FxMultiNameState::Emit(VMFunctionBuilder *build)
//
//==========================================================================
FxDamageValue::FxDamageValue(FxExpression *v, bool calc)
FxDamageValue::FxDamageValue(FxExpression *v)
: FxExpression(v->ScriptPosition)
{
val = v;
ValueType = TypeVoid;
Calculated = calc;
MyFunction = NULL;
if (!calc)
{
assert(v->isConstant() && "Non-calculated damage must be constant");
}
}
FxDamageValue::~FxDamageValue()
@ -5272,7 +5202,7 @@ ExpEmit FxDamageValue::Emit(VMFunctionBuilder *build)
assert(emitval.RegType == REGT_INT);
build->Emit(OP_RET, 0, REGT_INT | (emitval.Konst ? REGT_KONST : 0), emitval.RegNum);
}
build->Emit(OP_RETI, 1 | RET_FINAL, Calculated);
build->Emit(OP_RETI, 1 | RET_FINAL, true);
return ExpEmit();
}

View File

@ -865,19 +865,21 @@ static bool ParsePropertyParams(FScanner &sc, FPropertyInfo *prop, AActor *defau
if (sc.CheckString ("("))
{
x = new FxDamageValue(new FxIntCast(ParseExpression(sc, bag.Info)), true);
conv.i = -1;
params.Push(conv);
x = new FxDamageValue(new FxIntCast(ParseExpression(sc, bag.Info)));
sc.MustGetStringName(")");
conv.exp = x;
params.Push(conv);
}
else
{
sc.MustGetNumber();
if (sc.Number != 0)
{
x = new FxDamageValue(new FxConstant(sc.Number, bag.ScriptPosition), false);
}
conv.i = sc.Number;
params.Push(conv);
conv.exp = nullptr;
}
conv.exp = x;
params.Push(conv);
}
break;

View File

@ -636,7 +636,8 @@ DEFINE_PROPERTY(threshold, I, Actor)
//==========================================================================
DEFINE_PROPERTY(damage, X, Actor)
{
PROP_EXP_PARM(id, 0);
PROP_INT_PARM(dmgval, 0);
PROP_EXP_PARM(id, 1);
// Damage can either be a single number, in which case it is subject
// to the original damage calculation rules. Or, it can be an expression
@ -646,13 +647,15 @@ DEFINE_PROPERTY(damage, X, Actor)
// Store this expression here for now. It will be converted to a function
// later once all actors have been processed.
if (id == NULL)
defaults->DamageVal = dmgval;
if (id == nullptr)
{
defaults->Damage = NULL;
defaults->DamageFunc = nullptr;
}
else
{
defaults->Damage = (VMFunction *)(uintptr_t)(ActorDamageFuncs.Push(id) + 1);
defaults->DamageFunc = (VMFunction *)(uintptr_t)(ActorDamageFuncs.Push(id) + 1);
}
}