- scriptified Hexen's flies.

A few notes:

 * this accesses the lines array in sector_t which effectively is a pointer to an array of pointers - a type the parser can not represent. The compiler has no problems with it, so for now it is defined internally.
 * array sizes were limited to 65536 entries because the 'bound' instruction only existed as an immediate version with no provisions for larger values. For the static map arrays 65536 is not sufficient so now there are alternative instructions for these cases.
 * despite the above, at the moment there is no proper bounds checking for arrays that have no fixed size. To do this, a lot more work is needed. The type system as-is is not prepared for such a scenario.
This commit is contained in:
Christoph Oelckers 2016-11-27 18:52:24 +01:00
parent aab304c0cf
commit de6969997a
14 changed files with 241 additions and 45 deletions

View file

@ -154,6 +154,7 @@ AActor::~AActor ()
extern FFlagDef InternalActorFlagDefs[];
extern FFlagDef ActorFlagDefs[];
DEFINE_FIELD(AActor, snext)
DEFINE_FIELD(AActor, player)
DEFINE_FIELD_NAMED(AActor, __Pos, pos)
DEFINE_FIELD_NAMED(AActor, __Pos.X, x)

View file

@ -1310,3 +1310,19 @@ DEFINE_FIELD_X(Sector, sector_t, SecActTarget)
DEFINE_FIELD_X(Sector, sector_t, Portals)
DEFINE_FIELD_X(Sector, sector_t, PortalGroup)
DEFINE_FIELD_X(Sector, sector_t, sectornum)
DEFINE_FIELD_X(Line, line_t, v1)
DEFINE_FIELD_X(Line, line_t, v2)
DEFINE_FIELD_X(Line, line_t, delta)
DEFINE_FIELD_X(Line, line_t, flags)
DEFINE_FIELD_X(Line, line_t, activation)
DEFINE_FIELD_X(Line, line_t, special)
DEFINE_FIELD_X(Line, line_t, args)
DEFINE_FIELD_X(Line, line_t, alpha)
DEFINE_FIELD_X(Line, line_t, sidedef)
DEFINE_FIELD_X(Line, line_t, bbox)
DEFINE_FIELD_X(Line, line_t, frontsector)
DEFINE_FIELD_X(Line, line_t, backsector)
DEFINE_FIELD_X(Line, line_t, validcount)
DEFINE_FIELD_X(Line, line_t, locknumber)
DEFINE_FIELD_X(Line, line_t, portalindex)

View file

@ -1191,9 +1191,7 @@ struct side_t
struct line_t
{
vertex_t *v1, *v2; // vertices, from v1 to v2
private:
DVector2 delta; // precalculated v2 - v1 for side checking
public:
uint32_t flags;
uint32_t activation; // activation type
int special;

View file

@ -6578,9 +6578,16 @@ FxExpression *FxArrayElement::Resolve(FCompileContext &ctx)
PArray *arraytype = dyn_cast<PArray>(Array->ValueType);
if (arraytype == nullptr)
{
ScriptPosition.Message(MSG_ERROR, "'[]' can only be used with arrays.");
delete this;
return nullptr;
// Check if we got a pointer to an array. Some native data structures (like the line list in sectors) use this.
PPointer *ptype = dyn_cast<PPointer>(Array->ValueType);
if (ptype == nullptr || !ptype->PointedType->IsKindOf(RUNTIME_CLASS(PArray)))
{
ScriptPosition.Message(MSG_ERROR, "'[]' can only be used with arrays.");
delete this;
return nullptr;
}
arraytype = static_cast<PArray*>(ptype->PointedType);
arrayispointer = true;
}
if (index->isConstant())
{
@ -6592,37 +6599,40 @@ FxExpression *FxArrayElement::Resolve(FCompileContext &ctx)
return nullptr;
}
// if this is an array within a class or another struct we can simplify the expression by creating a new PField with a cumulative offset.
if (Array->ExprType == EFX_ClassMember || Array->ExprType == EFX_StructMember)
if (!arrayispointer)
{
auto parentfield = static_cast<FxStructMember *>(Array)->membervar;
// PFields are garbage collected so this will be automatically taken care of later.
auto newfield = new PField(NAME_None, arraytype->ElementType, parentfield->Flags, indexval * arraytype->ElementSize + parentfield->Offset);
static_cast<FxStructMember *>(Array)->membervar = newfield;
Array->isresolved = false; // re-resolve the parent so it can also check if it can be optimized away.
auto x = Array->Resolve(ctx);
Array = nullptr;
return x;
}
else if (Array->ExprType == EFX_GlobalVariable)
{
auto parentfield = static_cast<FxGlobalVariable *>(Array)->membervar;
auto newfield = new PField(NAME_None, arraytype->ElementType, parentfield->Flags, indexval * arraytype->ElementSize + parentfield->Offset);
static_cast<FxGlobalVariable *>(Array)->membervar = newfield;
Array->isresolved = false; // re-resolve the parent so it can also check if it can be optimized away.
auto x = Array->Resolve(ctx);
Array = nullptr;
return x;
}
else if (Array->ExprType == EFX_StackVariable)
{
auto parentfield = static_cast<FxStackVariable *>(Array)->membervar;
auto newfield = new PField(NAME_None, arraytype->ElementType, parentfield->Flags, indexval * arraytype->ElementSize + parentfield->Offset);
static_cast<FxStackVariable *>(Array)->ReplaceField(newfield);
Array->isresolved = false; // re-resolve the parent so it can also check if it can be optimized away.
auto x = Array->Resolve(ctx);
Array = nullptr;
return x;
// if this is an array within a class or another struct we can simplify the expression by creating a new PField with a cumulative offset.
if (Array->ExprType == EFX_ClassMember || Array->ExprType == EFX_StructMember)
{
auto parentfield = static_cast<FxStructMember *>(Array)->membervar;
// PFields are garbage collected so this will be automatically taken care of later.
auto newfield = new PField(NAME_None, arraytype->ElementType, parentfield->Flags, indexval * arraytype->ElementSize + parentfield->Offset);
static_cast<FxStructMember *>(Array)->membervar = newfield;
Array->isresolved = false; // re-resolve the parent so it can also check if it can be optimized away.
auto x = Array->Resolve(ctx);
Array = nullptr;
return x;
}
else if (Array->ExprType == EFX_GlobalVariable)
{
auto parentfield = static_cast<FxGlobalVariable *>(Array)->membervar;
auto newfield = new PField(NAME_None, arraytype->ElementType, parentfield->Flags, indexval * arraytype->ElementSize + parentfield->Offset);
static_cast<FxGlobalVariable *>(Array)->membervar = newfield;
Array->isresolved = false; // re-resolve the parent so it can also check if it can be optimized away.
auto x = Array->Resolve(ctx);
Array = nullptr;
return x;
}
else if (Array->ExprType == EFX_StackVariable)
{
auto parentfield = static_cast<FxStackVariable *>(Array)->membervar;
auto newfield = new PField(NAME_None, arraytype->ElementType, parentfield->Flags, indexval * arraytype->ElementSize + parentfield->Offset);
static_cast<FxStackVariable *>(Array)->ReplaceField(newfield);
Array->isresolved = false; // re-resolve the parent so it can also check if it can be optimized away.
auto x = Array->Resolve(ctx);
Array = nullptr;
return x;
}
}
}
@ -6644,8 +6654,17 @@ FxExpression *FxArrayElement::Resolve(FCompileContext &ctx)
ExpEmit FxArrayElement::Emit(VMFunctionBuilder *build)
{
PArray *arraytype;
if (arrayispointer)
{
arraytype = static_cast<PArray*>(static_cast<PPointer*>(Array->ValueType)->PointedType);
}
else
{
arraytype = static_cast<PArray*>(Array->ValueType);
}
ExpEmit start = Array->Emit(build);
PArray *const arraytype = static_cast<PArray*>(Array->ValueType);
/* what was this for?
if (start.Konst)
@ -6700,7 +6719,16 @@ ExpEmit FxArrayElement::Emit(VMFunctionBuilder *build)
else
{
ExpEmit indexv(index->Emit(build));
build->Emit(OP_BOUND, indexv.RegNum, arraytype->ElementCount);
// Todo: For dynamically allocated arrays (like global sector and linedef tables) we need to get the bound value in here somehow.
// Right now their bounds are not properly checked for.
if (arraytype->ElementCount > 65535)
{
build->Emit(OP_BOUND_K, indexv.RegNum, build->GetConstantInt(arraytype->ElementCount));
}
else
{
build->Emit(OP_BOUND, indexv.RegNum, arraytype->ElementCount);
}
if (!start.Konst)
{

View file

@ -1416,6 +1416,7 @@ public:
FxExpression *index;
bool AddressRequested;
bool AddressWritable;
bool arrayispointer = false;
FxArrayElement(FxExpression*, FxExpression*);
~FxArrayElement();

View file

@ -43,6 +43,7 @@
#include "d_player.h"
#include "p_effect.h"
#include "autosegs.h"
#include "p_maputl.h"
#include "gi.h"
static TArray<FPropertyInfo*> properties;
@ -703,7 +704,11 @@ void InitThingdef()
auto sptr = NewPointer(sstruct);
sstruct->AddNativeField("soundtarget", TypeActor, myoffsetof(sector_t, SoundTarget));
// expose the glibal Multiplayer variable.
// expose the global validcount variable.
PField *vcf = new PField("validcount", TypeSInt32, VARF_Native | VARF_Static, (intptr_t)&validcount);
GlobalSymbols.AddSymbol(vcf);
// expose the global Multiplayer variable.
PField *multif = new PField("multiplayer", TypeBool, VARF_Native | VARF_ReadOnly | VARF_Static, (intptr_t)&multiplayer);
GlobalSymbols.AddSymbol(multif);
@ -726,6 +731,11 @@ void InitThingdef()
PField *playerf = new PField("players", parray, VARF_Native | VARF_Static, (intptr_t)&players);
GlobalSymbols.AddSymbol(playerf);
// set up the lines array in the sector struct. This is a bit messy because the type system is not prepared to handle a pointer to an array of pointers to a native struct even remotely well...
// As a result, the size has to be set to something large and arbritrary because it can change between maps. This will need some serious improvement when things get cleaned up.
pstruct = NewNativeStruct("Sector", nullptr);
pstruct->AddNativeField("lines", NewPointer(NewArray(NewPointer(NewNativeStruct("line", nullptr), false), 0x40000), false), myoffsetof(sector_t, lines), VARF_Native);
parray = NewArray(TypeBool, MAXPLAYERS);
playerf = new PField("playeringame", parray, VARF_Native | VARF_Static | VARF_ReadOnly, (intptr_t)&playeringame);
GlobalSymbols.AddSymbol(playerf);

View file

@ -779,6 +779,7 @@ VMFunction *FFunctionBuildList::AddFunction(PFunction *functype, FxExpression *c
it.PrintableName = name;
it.Function = new VMScriptFunction;
it.Function->Name = functype->SymbolName;
it.Function->PrintableName = name;
it.Function->ImplicitArgs = functype->GetImplicitArgs();
it.Proto = nullptr;
it.FromDecorate = fromdecorate;
@ -881,7 +882,6 @@ void FFunctionBuildList::Build()
DumpFunction(dump, sfunc, item.PrintableName.GetChars(), (int)item.PrintableName.Len());
codesize += sfunc->CodeSize;
}
sfunc->PrintableName = item.PrintableName;
sfunc->Unsafe = ctx.Unsafe;
}
catch (CRecoverableError &err)

View file

@ -104,7 +104,6 @@
#define RIKIRI MODE_AI | MODE_BKI | MODE_CI
#define RIKII8 MODE_AI | MODE_BKI | MODE_CIMMZ
#define RIRIIs MODE_AI | MODE_BI | MODE_CIMMS
#define RIRI MODE_AI | MODE_BI | MODE_CUNUSED
#define I8RIRI MODE_AIMMZ | MODE_BI | MODE_CI
#define I8RIKI MODE_AIMMZ | MODE_BI | MODE_CKI
#define I8KIRI MODE_AIMMZ | MODE_BKI | MODE_CI

View file

@ -713,6 +713,7 @@ begin:
assert(0);
NEXTOP;
// Fixme: This really needs to throw something more informative than a number. Printing the message here instead of passing it to the exception is not sufficient.
OP(BOUND):
if (reg.d[a] >= BC)
{
@ -722,6 +723,26 @@ begin:
}
NEXTOP;
OP(BOUND_K):
ASSERTKD(BC);
if (reg.d[a] >= konstd[BC])
{
assert(false);
Printf("Array access out of bounds: Max. index = %u, current index = %u\n", konstd[BC], reg.d[a]);
THROW(X_ARRAY_OUT_OF_BOUNDS);
}
NEXTOP;
OP(BOUND_R):
ASSERTD(B);
if (reg.d[a] >= reg.d[B])
{
assert(false);
Printf("Array access out of bounds: Max. index = %u, current index = %u\n", reg.d[B], reg.d[a]);
THROW(X_ARRAY_OUT_OF_BOUNDS);
}
NEXTOP;
OP(CONCAT):
ASSERTS(a); ASSERTS(B); ASSERTS(C);
reg.s[a] = reg.s[B] + reg.s[C];

View file

@ -110,6 +110,8 @@ xx(CATCH, catch, CATCH, NOP, 0, 0), // A == 0: continue search on next try
// A == 3: (pkB == <type of exception thrown>) then pc++ ; next instruction must JMP to another CATCH
// for A > 0, exception is stored in pC
xx(BOUND, bound, RII16, NOP, 0, 0), // if rA >= BC, throw exception
xx(BOUND_K, bound, LKI, NOP, 0, 0), // if rA >= const[BC], throw exception
xx(BOUND_R, bound, RIRI, NOP, 0, 0), // if rA >= rB, throw exception
// String instructions.
xx(CONCAT, concat, RSRSRS, NOP, 0, 0), // sA = sB..sC

View file

@ -346,7 +346,9 @@ static void DoParse(int lumpnum)
}
}
#ifndef NDEBUG
if (f) fprintf(f, "Starting parsing %s\n", sc.String);
#endif
ParseSingleFile(sc.String, 0, parser, state);
}
}

View file

@ -11,6 +11,7 @@ class Actor : Thinker native
// flags are not defined here, the native fields for those get synthesized from the internal tables.
// for some comments on these fields, see their native representations in actor.h.
native readonly Actor snext; // next in sector list.
native PlayerInfo Player;
native readonly vector3 Pos;
native vector3 Prev;

View file

@ -129,6 +129,23 @@ struct F3DFloor native
{
}
struct Line native
{
//native readonly vertex v1, v2; // vertices, from v1 to v2
native readonly Vector2 delta; // precalculated v2 - v1 for side checking
native uint flags;
native uint activation; // activation type
native int special;
native int args[5]; // <--- hexen-style arguments (expanded to ZDoom's full width)
native double alpha; // <--- translucency (0=invisibile, FRACUNIT=opaque)
//native Side sidedef[2];
native readonly double bbox[4]; // bounding box, for the extent of the LineDef.
native readonly Sector frontsector, backsector;
native int validcount; // if == validcount, already checked
native int locknumber; // [Dusk] lock number for special
native readonly uint portalindex;
}
struct Sector native
{
//secplane_t floorplane, ceilingplane;
@ -145,7 +162,7 @@ struct Sector native
native readonly Vector2 centerspot;
native int validcount;
//AActor* thinglist;
native Actor thinglist;
native double friction, movefactor;
native int terrainnum[2];
@ -170,7 +187,7 @@ struct Sector native
native int nextsec;
native readonly int16 linecount;
//line_t **lines;
//line_t **lines; // this is defined internally to avoid exposing some overly complicated type to the parser
native readonly Sector heightsec;

View file

@ -15,9 +15,6 @@ class LittleFly : Actor
ActiveSound "FlyBuzz";
}
native void A_FlySearch();
native void A_FlyBuzz();
States
{
Spawn:
@ -27,4 +24,107 @@ class LittleFly : Actor
AFLY ABCD 3 A_FlyBuzz;
Loop;
}
//===========================================================================
//
// FindCorpse
//
// Finds a corpse to buzz around. We can't use a blockmap check because
// corpses generally aren't linked into the blockmap.
//
//===========================================================================
private Actor FindCorpse(sector sec, int recurselimit)
{
Actor fallback = null;
sec.validcount = validcount;
// Search the current sector
for (Actor check = sec.thinglist; check != null; check = check.snext)
{
if (check == self)
continue;
if (!check.bCorpse)
continue;
if (!CheckSight(check))
continue;
fallback = check;
if (random[Fly](0,1)) // 50% chance to try to pick a different corpse
continue;
return check;
}
if (--recurselimit <= 0 || (fallback != null && random[Fly](0,1)))
{
return fallback;
}
// Try neighboring sectors
for (int i = 0; i < sec.linecount; ++i)
{
line ln = sec.lines[i];
sector sec2 = (ln.frontsector == sec) ? ln.backsector : ln.frontsector;
if (sec2 != null && sec2.validcount != validcount)
{
Actor neighbor = FindCorpse(sec2, recurselimit);
if (neighbor != null)
{
return neighbor;
}
}
}
return fallback;
}
void A_FlySearch()
{
// The version from the retail beta is not so great for general use:
// 1. Pick one of the first fifty thinkers at random.
// 2. Starting from that thinker, find one that is an actor, not itself,
// and within sight. Give up after 100 sequential thinkers.
// It's effectively useless if there are more than 150 thinkers on a map.
//
// So search the sectors instead. We can't potentially find something all
// the way on the other side of the map and we can't find invisible corpses,
// but at least we aren't crippled on maps with lots of stuff going on.
validcount++;
Actor other = FindCorpse(CurSector, 5);
if (other != null)
{
target = other;
SetStateLabel("Buzz");
}
}
void A_FlyBuzz()
{
Actor targ = target;
if (targ == null || !targ.bCorpse || random[Fly]() < 5)
{
SetIdle();
return;
}
Angle = AngleTo(targ);
args[0]++;
if (!TryMove(Pos.XY + AngleToVector(angle, 6), true))
{
SetIdle(true);
return;
}
if (args[0] & 2)
{
Vel.X += (random[Fly]() - 128) / 512.;
Vel.Y += (random[Fly]() - 128) / 512.;
}
int zrand = random[Fly]();
if (targ.pos.Z + 5. < pos.z && zrand > 150)
{
zrand = -zrand;
}
Vel.Z = zrand / 512.;
if (random[Fly]() < 40)
{
A_PlaySound(ActiveSound, CHAN_VOICE, 0.5f, false, ATTN_STATIC);
}
}
}