diff --git a/src/p_mobj.cpp b/src/p_mobj.cpp index ad1c317e8b..358e46e8c1 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 841401226a..585c60f4bb 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 f33baeedc3..9c2a822f21 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 db09a0387a..f63629bb52 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 e586f1fbe3..5d0af0b49a 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 7a3d3a7c67..6f0cad099c 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 74b7a34000..f358fce7b6 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 cba67cbdc9..88962bf680 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 4df76a73e7..06c6ade8e1 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 b36f93881c..e5b6d34ebb 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 3b2ada4c8c..64ac2c86d2 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 739b5574fc..7b2b99570b 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 3766bb6b24..397ba4c325 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 464e6e9d94..d9800552ba 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); + } + } }