From de6969997aa088b5d3566be321e0bd835de5b8dd Mon Sep 17 00:00:00 2001 From: Christoph Oelckers Date: Sun, 27 Nov 2016 18:52:24 +0100 Subject: [PATCH] - 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. --- src/p_mobj.cpp | 1 + src/p_sectors.cpp | 16 ++++ src/r_defs.h | 2 - src/scripting/codegeneration/codegen.cpp | 98 +++++++++++++-------- src/scripting/codegeneration/codegen.h | 1 + src/scripting/thingdef_data.cpp | 12 ++- src/scripting/vm/vmbuilder.cpp | 2 +- src/scripting/vm/vmdisasm.cpp | 1 - src/scripting/vm/vmexec.h | 21 +++++ src/scripting/vm/vmops.h | 2 + src/scripting/zscript/zcc_parser.cpp | 2 + wadsrc/static/zscript/actor.txt | 1 + wadsrc/static/zscript/base.txt | 21 ++++- wadsrc/static/zscript/hexen/flies.txt | 106 ++++++++++++++++++++++- 14 files changed, 241 insertions(+), 45 deletions(-) diff --git a/src/p_mobj.cpp b/src/p_mobj.cpp index ad1c317e8..358e46e8c 100644 --- a/src/p_mobj.cpp +++ b/src/p_mobj.cpp @@ -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) diff --git a/src/p_sectors.cpp b/src/p_sectors.cpp index 841401226..585c60f4b 100644 --- a/src/p_sectors.cpp +++ b/src/p_sectors.cpp @@ -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) diff --git a/src/r_defs.h b/src/r_defs.h index f33baeedc..9c2a822f2 100644 --- a/src/r_defs.h +++ b/src/r_defs.h @@ -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; diff --git a/src/scripting/codegeneration/codegen.cpp b/src/scripting/codegeneration/codegen.cpp index db09a0387..f63629bb5 100644 --- a/src/scripting/codegeneration/codegen.cpp +++ b/src/scripting/codegeneration/codegen.cpp @@ -6578,9 +6578,16 @@ FxExpression *FxArrayElement::Resolve(FCompileContext &ctx) PArray *arraytype = dyn_cast(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(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(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(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(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(Array)->membervar; - auto newfield = new PField(NAME_None, arraytype->ElementType, parentfield->Flags, indexval * arraytype->ElementSize + parentfield->Offset); - static_cast(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(Array)->membervar; - auto newfield = new PField(NAME_None, arraytype->ElementType, parentfield->Flags, indexval * arraytype->ElementSize + parentfield->Offset); - static_cast(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(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(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(Array)->membervar; + auto newfield = new PField(NAME_None, arraytype->ElementType, parentfield->Flags, indexval * arraytype->ElementSize + parentfield->Offset); + static_cast(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(Array)->membervar; + auto newfield = new PField(NAME_None, arraytype->ElementType, parentfield->Flags, indexval * arraytype->ElementSize + parentfield->Offset); + static_cast(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(static_cast(Array->ValueType)->PointedType); + } + else + { + arraytype = static_cast(Array->ValueType); + } ExpEmit start = Array->Emit(build); - PArray *const arraytype = static_cast(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) { diff --git a/src/scripting/codegeneration/codegen.h b/src/scripting/codegeneration/codegen.h index e586f1fbe..5d0af0b49 100644 --- a/src/scripting/codegeneration/codegen.h +++ b/src/scripting/codegeneration/codegen.h @@ -1416,6 +1416,7 @@ public: FxExpression *index; bool AddressRequested; bool AddressWritable; + bool arrayispointer = false; FxArrayElement(FxExpression*, FxExpression*); ~FxArrayElement(); diff --git a/src/scripting/thingdef_data.cpp b/src/scripting/thingdef_data.cpp index 7a3d3a7c6..6f0cad099 100644 --- a/src/scripting/thingdef_data.cpp +++ b/src/scripting/thingdef_data.cpp @@ -43,6 +43,7 @@ #include "d_player.h" #include "p_effect.h" #include "autosegs.h" +#include "p_maputl.h" #include "gi.h" static TArray 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); diff --git a/src/scripting/vm/vmbuilder.cpp b/src/scripting/vm/vmbuilder.cpp index 74b7a3400..f358fce7b 100644 --- a/src/scripting/vm/vmbuilder.cpp +++ b/src/scripting/vm/vmbuilder.cpp @@ -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) diff --git a/src/scripting/vm/vmdisasm.cpp b/src/scripting/vm/vmdisasm.cpp index cba67cbdc..88962bf68 100644 --- a/src/scripting/vm/vmdisasm.cpp +++ b/src/scripting/vm/vmdisasm.cpp @@ -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 diff --git a/src/scripting/vm/vmexec.h b/src/scripting/vm/vmexec.h index 4df76a73e..06c6ade8e 100644 --- a/src/scripting/vm/vmexec.h +++ b/src/scripting/vm/vmexec.h @@ -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]; diff --git a/src/scripting/vm/vmops.h b/src/scripting/vm/vmops.h index b36f93881..e5b6d34eb 100644 --- a/src/scripting/vm/vmops.h +++ b/src/scripting/vm/vmops.h @@ -110,6 +110,8 @@ xx(CATCH, catch, CATCH, NOP, 0, 0), // A == 0: continue search on next try // A == 3: (pkB == ) 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 diff --git a/src/scripting/zscript/zcc_parser.cpp b/src/scripting/zscript/zcc_parser.cpp index 3b2ada4c8..64ac2c86d 100644 --- a/src/scripting/zscript/zcc_parser.cpp +++ b/src/scripting/zscript/zcc_parser.cpp @@ -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); } } diff --git a/wadsrc/static/zscript/actor.txt b/wadsrc/static/zscript/actor.txt index 739b5574f..7b2b99570 100644 --- a/wadsrc/static/zscript/actor.txt +++ b/wadsrc/static/zscript/actor.txt @@ -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; diff --git a/wadsrc/static/zscript/base.txt b/wadsrc/static/zscript/base.txt index 3766bb6b2..397ba4c32 100644 --- a/wadsrc/static/zscript/base.txt +++ b/wadsrc/static/zscript/base.txt @@ -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; diff --git a/wadsrc/static/zscript/hexen/flies.txt b/wadsrc/static/zscript/hexen/flies.txt index 464e6e9d9..d9800552b 100644 --- a/wadsrc/static/zscript/hexen/flies.txt +++ b/wadsrc/static/zscript/hexen/flies.txt @@ -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); + } + } }