mirror of
https://github.com/ZDoom/qzdoom.git
synced 2024-11-28 06:53:58 +00:00
- 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:
parent
aab304c0cf
commit
de6969997a
14 changed files with 241 additions and 45 deletions
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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)
|
||||
{
|
||||
|
|
|
@ -1416,6 +1416,7 @@ public:
|
|||
FxExpression *index;
|
||||
bool AddressRequested;
|
||||
bool AddressWritable;
|
||||
bool arrayispointer = false;
|
||||
|
||||
FxArrayElement(FxExpression*, FxExpression*);
|
||||
~FxArrayElement();
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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];
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue