diff --git a/doc/specs/udmf_srb2.txt b/doc/specs/udmf_srb2.txt index b25ed1af38ee55b6c2ecf568c824520d800c58e7..93746e547b092a0a07748e9b6e5a716a5c42548f 100644 --- a/doc/specs/udmf_srb2.txt +++ b/doc/specs/udmf_srb2.txt @@ -33,7 +33,16 @@ II.A : Storage and Retrieval of Data II.B : Storage Within Archive Files ----------------------------------- - No changes. +In addition to the base specification SRB2 recognizes the following lumps +between the TEXTMAP and ENDMAP lumps: + + BEHAVIOR = contains compiled ACS code + ZNODES = Nodes (must be stored as extended GL nodes.) + BLOCKMAP = blockmap. It is recommended not to include this lump in UDMF maps. + REJECT = reject table. It is recommended not to include this lump in UDMF maps. + + Lumps starting with 'SCRIPT' are guaranteed to be ignored by SRB2 so they + can be used to store ACS and dialogue script sources. -------------------------------- II.C : Implementation Dependence @@ -45,6 +54,12 @@ The SRB2 engine only supports the following namespace: The engine is allowed to refuse maps with an unsupported namespace, or emit a warning. +Additionally, maps may include a "version" field, represented as an integer. +This must be increased by one whenever backwards-incompatible changes are introduced. + +The highest version currently supported by the engine is: + 0 + ======================================= III. Standardized Fields ======================================= @@ -86,6 +101,14 @@ Sonic Robo Blast 2 defines the following standardized fields: bouncy = <bool>; // Line is bouncy. transfer = <bool>; // In 3D floor sides, uses the sidedef properties of the control sector. + playercross = <bool>; // Special activates when a player crosses this line. + monstercross = <bool>; // Special activates when an enemy or boss crosses this line. + missilecross = <bool>; // Special activates when a projectile crosses this line. + playerpush = <bool>; // Special activates when a player pushes this line. + monsterpush = <bool>; // Special activates when an enemy or boss pushed this line. + impact = <bool>; // Special activates when a projectile hits this line. + repeatspecial = <bool>; // Special is repeatable. + alpha = <float>; // Translucency of this line. Default is 1.0 renderstyle = <string>; // Render style. Can be: // - "translucent" @@ -203,6 +226,18 @@ Sonic Robo Blast 2 defines the following standardized fields: triggerline_plane = <bool>; // Trigger effects require a plane touch to be executed. triggerline_mobj = <bool>; // Trigger effects can be executed by non-pushable objects. + playerenter = <bool>; // Special activates when a player enters this sector. + playerfloor = <bool>; // Special activates when a player's feet touches the sector's floor. + playerceiling = <bool>; // Special activates when a player's head touches the sector's ceiling. + monsterenter = <bool>; // Special activates when an enemy or boss enters this sector. + monsterfloor = <bool>; // Special activates when an enemy or boss's feet touches the sector's floor. + monsterceiling = <bool>; // Special activates when an enemy or boss's head touches the sector's ceiling. + missileenter = <bool>; // Special activates when a missile enters this sector. + missilefloor = <bool>; // Special activates when a missile hits the sector's floor. + missileceiling = <bool>; // Special activates when a missile hits the sector's ceiling. + repeatspecial = <bool>; // Special is repeatable. + continuousspecial = <bool>; // Special is activated continuously. + invertprecip = <bool>; // Inverts the precipitation effect; if the sector is considered to be indoors, // precipitation is generated, and if the sector is considered to be outdoors, // precipitation is not generated. @@ -296,6 +331,20 @@ Sonic Robo Blast 2 defines the following standardized fields: stringarg0 = <string>; // String argument 0. stringarg1 = <string>; // String argument 1. + special = <integer>; // Special. Default = 0. + scriptarg0 = <integer>; // Special argument 0. Default = 0. + scriptarg1 = <integer>; // Special argument 1. Default = 0. + scriptarg2 = <integer>; // Special argument 2. Default = 0. + scriptarg3 = <integer>; // Special argument 3. Default = 0. + scriptarg4 = <integer>; // Special argument 4. Default = 0. + scriptarg5 = <integer>; // Special argument 5. Default = 0. + scriptarg6 = <integer>; // Special argument 6. Default = 0. + scriptarg7 = <integer>; // Special argument 7. Default = 0. + scriptarg8 = <integer>; // Special argument 8. Default = 0. + scriptarg9 = <integer>; // Special argument 9. Default = 0. + scriptstringarg0 = <string>; // Special string argument 0. + scriptstringarg1 = <string>; // Special string argument 1. + comment = <string>; // A comment. Implementors should attach no special // semantic meaning to this field. } @@ -305,6 +354,10 @@ Sonic Robo Blast 2 defines the following standardized fields: Changelog ======================================= +1.1: 25.04.2024 +Added activation parameters for lines and sectors. +Added special and script arguments for things. + 1.0: 19.02.2024 Initial version. diff --git a/extras/acs/srb2common.acs b/extras/acs/srb2common.acs new file mode 100644 index 0000000000000000000000000000000000000000..32117409569d5d4c338c15c11162eb92900aa561 --- /dev/null +++ b/extras/acs/srb2common.acs @@ -0,0 +1,4 @@ +// Copyright (C) 2024 Russell's Smart Interfaces + +#include "srb2special.acs" +#include "srb2defs.acs" diff --git a/extras/acs/srb2defs.acs b/extras/acs/srb2defs.acs new file mode 100644 index 0000000000000000000000000000000000000000..895b8e7e41a7c73243a291271a81a07ee2f5f352 --- /dev/null +++ b/extras/acs/srb2defs.acs @@ -0,0 +1,357 @@ +// Copyright (C) 2024 Russell's Smart Interfaces + +#define TRUE 1 +#define FALSE 0 +#define ON 1 +#define OFF 0 +#define YES 1 +#define NO 0 + +#define FRACUNIT 65536 +#define TICRATE 35 + +#define LINE_FRONT 0 +#define LINE_BACK 1 + +#define SIDE_FRONT 0 +#define SIDE_BACK 1 + +#define TEXTURE_TOP 0 +#define TEXTURE_MIDDLE 1 +#define TEXTURE_BOTTOM 2 + +#define GAME_COOPERATIVE 0 +#define GAME_COMPETITION 1 +#define GAME_RACE 2 +#define GAME_MATCH 3 +#define GAME_TEAMMATCH 4 +#define GAME_TAG 5 +#define GAME_HIDEANDSEEK 6 +#define GAME_CTF 7 + +// Actor properties you can get/set ----------------------------------------- + +#define APROP_X 0 +#define APROP_Y 1 +#define APROP_Z 2 +#define APROP_Type 3 +#define APROP_Angle 4 +#define APROP_Pitch 5 +#define APROP_Roll 6 +#define APROP_SpriteRoll 7 +#define APROP_Frame 8 +#define APROP_Sprite 9 +#define APROP_Sprite2 10 +#define APROP_RenderFlags 11 +#define APROP_SpriteXScale 12 +#define APROP_SpriteYScale 13 +#define APROP_SpriteXOffset 14 +#define APROP_SpriteYOffset 15 +#define APROP_FloorZ 16 +#define APROP_CeilingZ 17 +#define APROP_Radius 18 +#define APROP_Height 19 +#define APROP_MomX 20 +#define APROP_MomY 21 +#define APROP_MomZ 22 +#define APROP_Tics 23 +#define APROP_State 24 +#define APROP_Flags 25 +#define APROP_Flags2 26 +#define APROP_ExtraFlags 27 +#define APROP_Skin 28 +#define APROP_Color 29 +#define APROP_Health 30 +#define APROP_MoveDir 31 +#define APROP_MoveCount 32 +#define APROP_ReactionTime 33 +#define APROP_Threshold 34 +#define APROP_LastLook 35 +#define APROP_Friction 36 +#define APROP_MoveFactor 37 +#define APROP_Fuse 38 +#define APROP_WaterTop 39 +#define APROP_WaterBottom 40 +#define APROP_Scale 41 +#define APROP_DestScale 42 +#define APROP_ScaleSpeed 43 +#define APROP_ExtraValue1 44 +#define APROP_ExtraValue2 45 +#define APROP_CustomVal 46 +#define APROP_CustomValMem 47 +#define APROP_Colorized 48 +#define APROP_Mirrored 49 +#define APROP_ShadowScale 50 +#define APROP_DispOffset 51 +#define APROP_Target 52 +#define APROP_Tracer 53 +#define APROP_HNext 54 +#define APROP_HPrev 55 + +// Line properties + +#define LINEPROP_Flags 0 +#define LINEPROP_Alpha 1 +#define LINEPROP_Style 2 +#define LINEPROP_Activation 3 +#define LINEPROP_Action 4 +#define LINEPROP_Arg0 5 +#define LINEPROP_Arg1 6 +#define LINEPROP_Arg2 7 +#define LINEPROP_Arg3 8 +#define LINEPROP_Arg4 9 +#define LINEPROP_Arg5 10 +#define LINEPROP_Arg6 11 +#define LINEPROP_Arg7 12 +#define LINEPROP_Arg8 13 +#define LINEPROP_Arg9 14 +#define LINEPROP_Arg0Str 15 +#define LINEPROP_Arg1Str 16 + +// Side properties + +#define SIDEPROP_XOffset 0 +#define SIDEPROP_YOffset 1 +#define SIDEPROP_TopTexture 2 +#define SIDEPROP_BottomTexture 3 +#define SIDEPROP_MidTexture 4 +#define SIDEPROP_RepeatCount 5 + +// Sector properties + +#define SECPROP_FloorHeight 1 +#define SECPROP_CeilingHeight 2 +#define SECPROP_FloorPic 3 +#define SECPROP_CeilingPic 4 +#define SECPROP_LightLevel 5 +#define SECPROP_FloorLightLevel 6 +#define SECPROP_CeilingLightLevel 7 +#define SECPROP_FloorLightAbsolute 8 +#define SECPROP_CeilingLightAbsolute 9 +#define SECPROP_Flags 10 +#define SECPROP_SpecialFlags 11 +#define SECPROP_Gravity 12 +#define SECPROP_Activation 13 +#define SECPROP_Action 14 +#define SECPROP_Arg0 15 +#define SECPROP_Arg1 16 +#define SECPROP_Arg2 17 +#define SECPROP_Arg3 18 +#define SECPROP_Arg4 19 +#define SECPROP_Arg5 20 +#define SECPROP_Arg6 21 +#define SECPROP_Arg7 22 +#define SECPROP_Arg8 23 +#define SECPROP_Arg9 24 +#define SECPROP_Arg0Str 25 +#define SECPROP_Arg1Str 26 + +// Render Styles ------------------------------------------------------------ + +#define STYLE_Copy 0 // Just copy the image to the screen +#define STYLE_Translucent 1 // Draw translucent +#define STYLE_Add 2 // Draw additive +#define STYLE_Subtract 3 // Draw subtractive +#define STYLE_ReverseSubtract 4 // Draw reverse subtractive +#define STYLE_Modulate 5 // Draw multiplicative +#define STYLE_Fog 7 // Draw reverse subtractive + +// Line activation flags + +#define SPAC_None 0 +#define SPAC_Repeat 1 // repeatable +#define SPAC_Cross 2 // when player crosses line +#define SPAC_MCross 4 // when monster crosses line +#define SPAC_PCross 8 // when projectile crosses line +#define SPAC_Push 16 // when player pushes line +#define SPAC_MPush 32 // monsters can push +#define SPAC_Impact 64 // when projectile hits line + +// Sector activation flags + +#define SECSPAC_None 0 +#define SECSPAC_OnceSpecial 1 // special action is activated once +#define SECSPAC_RepeatSpecial 2 // special action is repeatable +#define SECSPAC_ContinuousSpecial 4 // special action is activated continously +#define SECSPAC_Enter 8 // when a player enters this sector +#define SECSPAC_Floor 16 // when a player touches the floor of this sector +#define SECSPAC_Ceiling 32 // when a player touches the ceiling of this sector +#define SECSPAC_EnterMonster 64 // when an enemy enters this sector +#define SECSPAC_FLOORMonster 128 // when an enemy touches the floor of this sector +#define SECSPAC_CeilingMonster 256 // when an enemy touches the ceiling of this sector +#define SECSPAC_EnterMissile 512 // when a projectile enters this sector +#define SECSPAC_FloorMissile 1024 // when a projectile touches the floor of this sector +#define SECSPAC_CeilingMissile 2048 // when a projectile touches the ceiling of this sector + +// Teams ----------------------------------------------------------- + +#define NO_TEAM 0 +#define TEAM_RED 1 +#define TEAM_BLUE 2 + +// Bot types +#define BOT_NONE 0 +#define BOT_2PAI 1 +#define BOT_2PHUMAN 2 +#define BOT_MPAI 3 + +// Colors ---------------------------------------------------------- + +#define SKINCOLOR_NONE 0 +#define SKINCOLOR_WHITE 1 +#define SKINCOLOR_BONE 2 +#define SKINCOLOR_CLOUDY 3 +#define SKINCOLOR_GREY 4 +#define SKINCOLOR_SILVER 5 +#define SKINCOLOR_CARBON 6 +#define SKINCOLOR_JET 7 +#define SKINCOLOR_BLACK 8 +#define SKINCOLOR_AETHER 9 +#define SKINCOLOR_SLATE 10 +#define SKINCOLOR_MOONSTONE 11 +#define SKINCOLOR_BLUEBELL 12 +#define SKINCOLOR_PINK 13 +#define SKINCOLOR_ROSEWOOD 14 +#define SKINCOLOR_YOGURT 15 +#define SKINCOLOR_LATTE 16 +#define SKINCOLOR_BROWN 17 +#define SKINCOLOR_BOULDER 18 +#define SKINCOLOR_BRONZE 19 +#define SKINCOLOR_SEPIA 20 +#define SKINCOLOR_ECRU 21 +#define SKINCOLOR_TAN 22 +#define SKINCOLOR_BEIGE 23 +#define SKINCOLOR_ROSEBUSH 24 +#define SKINCOLOR_MOSS 25 +#define SKINCOLOR_AZURE 26 +#define SKINCOLOR_EGGPLANT 27 +#define SKINCOLOR_LAVENDER 28 +#define SKINCOLOR_RUBY 29 +#define SKINCOLOR_CHERRY 30 +#define SKINCOLOR_SALMON 31 +#define SKINCOLOR_PEPPER 32 +#define SKINCOLOR_RED 33 +#define SKINCOLOR_CRIMSON 34 +#define SKINCOLOR_FLAME 35 +#define SKINCOLOR_GARNET 36 +#define SKINCOLOR_KETCHUP 37 +#define SKINCOLOR_PEACHY 38 +#define SKINCOLOR_QUAIL 39 +#define SKINCOLOR_FOUNDATION 40 +#define SKINCOLOR_SUNSET 41 +#define SKINCOLOR_COPPER 42 +#define SKINCOLOR_APRICOT 43 +#define SKINCOLOR_ORANGE 44 +#define SKINCOLOR_RUST 45 +#define SKINCOLOR_TANGERINE 46 +#define SKINCOLOR_TOPAZ 47 +#define SKINCOLOR_GOLD 48 +#define SKINCOLOR_SANDY 49 +#define SKINCOLOR_GOLDENROD 50 +#define SKINCOLOR_YELLOW 51 +#define SKINCOLOR_OLIVE 52 +#define SKINCOLOR_PEAR 53 +#define SKINCOLOR_LEMON 54 +#define SKINCOLOR_LIME 55 +#define SKINCOLOR_PERIDOT 56 +#define SKINCOLOR_APPLE 57 +#define SKINCOLOR_HEADLIGHT 58 +#define SKINCOLOR_CHARTREUSE 59 +#define SKINCOLOR_GREEN 60 +#define SKINCOLOR_FOREST 61 +#define SKINCOLOR_SHAMROCK 62 +#define SKINCOLOR_JADE 63 +#define SKINCOLOR_MINT 64 +#define SKINCOLOR_MASTER 65 +#define SKINCOLOR_EMERALD 66 +#define SKINCOLOR_SEAFOAM 67 +#define SKINCOLOR_ISLAND 68 +#define SKINCOLOR_BOTTLE 69 +#define SKINCOLOR_AQUA 70 +#define SKINCOLOR_TEAL 71 +#define SKINCOLOR_OCEAN 72 +#define SKINCOLOR_WAVE 73 +#define SKINCOLOR_CYAN 74 +#define SKINCOLOR_TURQUOISE 75 +#define SKINCOLOR_AQUAMARINE 76 +#define SKINCOLOR_SKY 77 +#define SKINCOLOR_MARINE 78 +#define SKINCOLOR_CERULEAN 79 +#define SKINCOLOR_DREAM 80 +#define SKINCOLOR_ICY 81 +#define SKINCOLOR_DAYBREAK 82 +#define SKINCOLOR_SAPPHIRE 83 +#define SKINCOLOR_ARCTIC 84 +#define SKINCOLOR_CORNFLOWER 85 +#define SKINCOLOR_BLUE 86 +#define SKINCOLOR_COBALT 87 +#define SKINCOLOR_MIDNIGHT 88 +#define SKINCOLOR_GALAXY 89 +#define SKINCOLOR_VAPOR 90 +#define SKINCOLOR_DUSK 91 +#define SKINCOLOR_MAJESTY 92 +#define SKINCOLOR_PASTEL 93 +#define SKINCOLOR_PURPLE 94 +#define SKINCOLOR_NOBLE 95 +#define SKINCOLOR_FUCHSIA 96 +#define SKINCOLOR_BUBBLEGUM 97 +#define SKINCOLOR_SIBERITE 98 +#define SKINCOLOR_MAGENTA 99 +#define SKINCOLOR_NEON 100 +#define SKINCOLOR_VIOLET 101 +#define SKINCOLOR_ROYAL 102 +#define SKINCOLOR_LILAC 103 +#define SKINCOLOR_MAUVE 104 +#define SKINCOLOR_EVENTIDE 105 +#define SKINCOLOR_PLUM 106 +#define SKINCOLOR_RASPBERRY 107 +#define SKINCOLOR_TAFFY 108 +#define SKINCOLOR_ROSY 109 +#define SKINCOLOR_FANCY 110 +#define SKINCOLOR_SANGRIA 111 +#define SKINCOLOR_VOLCANIC 112 +#define SKINCOLOR_SUPERSILVER1 113 +#define SKINCOLOR_SUPERSILVER2 114 +#define SKINCOLOR_SUPERSILVER3 115 +#define SKINCOLOR_SUPERSILVER4 116 +#define SKINCOLOR_SUPERSILVER5 117 +#define SKINCOLOR_SUPERRED1 118 +#define SKINCOLOR_SUPERRED2 119 +#define SKINCOLOR_SUPERRED3 120 +#define SKINCOLOR_SUPERRED4 121 +#define SKINCOLOR_SUPERRED5 122 +#define SKINCOLOR_SUPERORANGE1 123 +#define SKINCOLOR_SUPERORANGE2 124 +#define SKINCOLOR_SUPERORANGE3 125 +#define SKINCOLOR_SUPERORANGE4 126 +#define SKINCOLOR_SUPERORANGE5 127 +#define SKINCOLOR_SUPERGOLD1 128 +#define SKINCOLOR_SUPERGOLD2 129 +#define SKINCOLOR_SUPERGOLD3 130 +#define SKINCOLOR_SUPERGOLD4 131 +#define SKINCOLOR_SUPERGOLD5 132 +#define SKINCOLOR_SUPERPERIDOT1 133 +#define SKINCOLOR_SUPERPERIDOT2 134 +#define SKINCOLOR_SUPERPERIDOT3 135 +#define SKINCOLOR_SUPERPERIDOT4 136 +#define SKINCOLOR_SUPERPERIDOT5 137 +#define SKINCOLOR_SUPERSKY1 138 +#define SKINCOLOR_SUPERSKY2 139 +#define SKINCOLOR_SUPERSKY3 140 +#define SKINCOLOR_SUPERSKY4 141 +#define SKINCOLOR_SUPERSKY5 142 +#define SKINCOLOR_SUPERPURPLE1 143 +#define SKINCOLOR_SUPERPURPLE2 144 +#define SKINCOLOR_SUPERPURPLE3 145 +#define SKINCOLOR_SUPERPURPLE4 146 +#define SKINCOLOR_SUPERPURPLE5 147 +#define SKINCOLOR_SUPERRUST1 148 +#define SKINCOLOR_SUPERRUST2 149 +#define SKINCOLOR_SUPERRUST3 150 +#define SKINCOLOR_SUPERRUST4 151 +#define SKINCOLOR_SUPERRUST5 152 +#define SKINCOLOR_SUPERTAN1 153 +#define SKINCOLOR_SUPERTAN2 154 +#define SKINCOLOR_SUPERTAN3 155 +#define SKINCOLOR_SUPERTAN4 156 +#define SKINCOLOR_SUPERTAN5 157 diff --git a/extras/acs/srb2special.acs b/extras/acs/srb2special.acs new file mode 100644 index 0000000000000000000000000000000000000000..cc6839a797bb6a0f5f55f2a947639c73757b704f --- /dev/null +++ b/extras/acs/srb2special.acs @@ -0,0 +1,116 @@ +// Copyright (C) 2024 Russell's Smart Interfaces + +special + -1:GetLineProperty(2), + -2:SetLineProperty(3), + -4:GetSectorProperty(2), + -5:SetSectorProperty(3), + -7:GetSideProperty(2), + -8:SetSideProperty(3), + -10:GetThingProperty(2), + -11:SetThingProperty(3), + -100:strcmp(2,3), + -101:strcasecmp(2,3), + -300:CountEnemies(2), + -301:CountPushables(2), + -303:HaveUnlockable(1), + -304:PlayerSkin(0), + -305:GetObjectDye(0), + -306:PlayerEmeralds(0), + -307:PlayerLap(0), + -308:LowestLap(0), + -309:RingSlingerMode(0), + -310:TeamGame(0), + -311:RecordAttack(0), + -312:Thing_Dye(1), + -313:CaptureTheFlagMode(0), + -315:PlayerBot(0), + -316:ModeAttacking(0), + -317:NiGHTSAttack(0), + -320:PlayerExiting(0), + -500:CameraWait(1), + -503:SetLineRenderStyle(3), + -504:MapWarp(2), + -505:AddBot(1, 4), + -507:ExitLevel(0,1), + -508:MusicPlay(1,2), + -509:MusicStopAll(0,1), + -510:MusicRestore(0), + -512:MusicDim(1), + // -700:AddMessage(1), + // -701:AddMessageForPlayer(1), + + // SRB2 linedef types (400-499) + // Not all are implemented + 400:Floor_SetHeight(2), + 401:Ceiling_SetHeight(2), + 402:Light_ChangeToValue(2), + 403:Floor_Move(2), + 404:Ceiling_Move(2), + 409:Sector_ChangeTag(3), + 411:Plane_Stop(1), + // 412:Teleport(2,5), // to reimplement + 416:Light_StartFlickering(3,5), + 417:Light_StartPulsating(3,5), + 418:Light_StartBlinking(3,6), + 419:Light_StartBlinkingSynchronized(3,6), + 420:Light_Fade(3,6), + 421:StopLightingEffect(1), + // 422:SwitchToCutAwayView(2), // should be reimplemented to search by TID + // 423:ChangeSky(1,2), // already implemented + 424:Weather_Change(1,2), + 425:Thing_ChangeState(1), + 426:Thing_Stop(0,1), + 427:AwardScore(1), + 428:Plat_StartMovement(1,2), + 429:Sector_Crush(1,2), + 432:Switch2DMode(0,1), + 433:GravityFlip(0,2), + // 434:AwardPowerUp(2), // to reimplement + 435:Plane_ChangeScrollerDirection(2), + 436:FOF_Shatter(2), + 437:Player_DisableControls(1,2), + 438:Thing_ChangeSize(1), + 439:Line_ChangeTextures(2,4), + 440:StartMetalSonicRace(0), + 441:ConditionSetTrigger(1), + 443:CallLuaFunction(1), + 444:Earthquake(1,3), + 445:FOF_Disappear(2,3), + 446:FOF_Crumble(2,3), + 447:Colormap_Change(1,3), + 448:Skybox_Change(2,4), + 449:EnableBosses(1,2), + 450:LinedefExecute(1), + 451:LinedefExecuteRandom(2), + 452:FOF_SetTranslucency(3), + 453:FOF_Fade(4), + 454:FOF_StopFading(2), + 455:Colormap_Fade(3,4), + 456:Colormap_StopFading(1), + // 457:Thing_TrackAngle(4,5), // should be reimplemented to search by TID + // 458:Thing_StopTrackingAngle(0), + // 459:StartConversation(), // to reimplement + 460:AwardRings(2), + // 461:Thing_Spawn(2), // to reimplement + 462:StopTimer(0), + // 463:Thing_Dye(1), // Reimplemented as SetObjectDye + 464:TriggerEggCapsule(1,2), + // 466:SetLevelFailureState(0,1), // to reimplement + 475:ACS_NamedExecute(1,10), + 476:ACS_NamedExecuteAlways(1,10), + 477:ACS_NamedSuspend(1), + 478:ACS_NamedTerminate(1), + 480:Polyobj_DoorSlide(0), + 481:Polyobj_DoorSwing(0), + 482:Polyobj_Move(0), + 483:Polyobj_MoveOverride(0), + 484:Polyobj_RotateRight(0), + 485:Polyobj_RotateRightOverride(0), + 486:Polyobj_RotateLeft(0), + 487:Polyobj_RotateLeftOverride(0), + 488:Polyobj_WaypointMove(0), + 489:Polyobj_TurnInvisibleIntangible(0), + 490:Polyobj_TurnVisibleTangible(0), + 491:Polyobj_SetTranslucency(0), + 492:Polyobj_FadeTranslucency(0); diff --git a/extras/conf/udb/Includes/SRB222_linedefs.cfg b/extras/conf/udb/Includes/SRB222_linedefs.cfg index e297f473fd46d2848f3835765988bb913147e84f..8f16d6c14e2969aba843a62308f7c68121f5882f 100644 --- a/extras/conf/udb/Includes/SRB222_linedefs.cfg +++ b/extras/conf/udb/Includes/SRB222_linedefs.cfg @@ -3413,6 +3413,294 @@ udmf enum = "setadd"; } } + + 475 + { + title = "Script Execute"; + id = "ACS_Execute"; + + arg0 + { + str = true; + titlestr = "Script Name"; + } + + arg1 + { + title = "Script Argument 1"; + } + + arg2 + { + title = "Script Argument 2"; + } + + arg3 + { + title = "Script Argument 3"; + } + + arg4 + { + title = "Script Argument 4"; + } + + arg5 + { + title = "Script Argument 5"; + } + + arg6 + { + title = "Script Argument 6"; + } + + arg7 + { + title = "Script Argument 7"; + } + + arg8 + { + title = "Script Argument 8"; + } + + arg9 + { + title = "Script Argument 9"; + } + + arg10 + { + title = "Script Argument 10"; + } + + stringarg0 + { + title = "Script String Argument 1"; + } + + stringarg1 + { + title = "Script String Argument 2"; + } + } + + 476 + { + title = "Script Execute Always"; + id = "ACS_ExecuteAlways"; + + arg0 + { + str = true; + titlestr = "Script Name"; + } + + arg1 + { + title = "Script Argument 1"; + } + + arg2 + { + title = "Script Argument 2"; + } + + arg3 + { + title = "Script Argument 3"; + } + + arg4 + { + title = "Script Argument 4"; + } + + arg5 + { + title = "Script Argument 5"; + } + + arg6 + { + title = "Script Argument 6"; + } + + arg7 + { + title = "Script Argument 7"; + } + + arg8 + { + title = "Script Argument 8"; + } + + arg9 + { + title = "Script Argument 9"; + } + + arg10 + { + title = "Script Argument 10"; + } + + stringarg0 + { + title = "Script String Argument 1"; + } + + stringarg1 + { + title = "Script String Argument 2"; + } + } + + 477 + { + title = "Script Suspend"; + id = "ACS_Suspend"; + + arg0 + { + str = true; + titlestr = "Script Name"; + } + + arg1 + { + title = "Script Argument 1"; + } + + arg2 + { + title = "Script Argument 2"; + } + + arg3 + { + title = "Script Argument 3"; + } + + arg4 + { + title = "Script Argument 4"; + } + + arg5 + { + title = "Script Argument 5"; + } + + arg6 + { + title = "Script Argument 6"; + } + + arg7 + { + title = "Script Argument 7"; + } + + arg8 + { + title = "Script Argument 8"; + } + + arg9 + { + title = "Script Argument 9"; + } + + arg10 + { + title = "Script Argument 10"; + } + + stringarg0 + { + title = "Script String Argument 1"; + } + + stringarg1 + { + title = "Script String Argument 2"; + } + } + + 478 + { + title = "Script Terminate"; + id = "ACS_Terminate"; + + arg0 + { + str = true; + titlestr = "Script Name"; + } + + arg1 + { + title = "Script Argument 1"; + } + + arg2 + { + title = "Script Argument 2"; + } + + arg3 + { + title = "Script Argument 3"; + } + + arg4 + { + title = "Script Argument 4"; + } + + arg5 + { + title = "Script Argument 5"; + } + + arg6 + { + title = "Script Argument 6"; + } + + arg7 + { + title = "Script Argument 7"; + } + + arg8 + { + title = "Script Argument 8"; + } + + arg9 + { + title = "Script Argument 9"; + } + + arg10 + { + title = "Script Argument 10"; + } + + stringarg0 + { + title = "Script String Argument 1"; + } + + stringarg1 + { + title = "Script String Argument 2"; + } + } } linedefexecpoly diff --git a/extras/conf/udb/Includes/SRB222_misc.cfg b/extras/conf/udb/Includes/SRB222_misc.cfg index 0b94a5a87a37a5ce7cf6a2a4c9e5799f7c738eab..097d3eb6aa915afb6bfc88516219120a2baac5b9 100644 --- a/extras/conf/udb/Includes/SRB222_misc.cfg +++ b/extras/conf/udb/Includes/SRB222_misc.cfg @@ -40,6 +40,21 @@ linedefflags_udmf transfer = "Transfer Line"; } +linedefactivations_udmf +{ + repeatspecial + { + name = "Repeatable action"; + istrigger = false; + } + playercross = "When player walks over"; + playerpush = "When player bumps"; + monstercross = "When enemy walks over"; + monsterpush = "When enemies bumps"; + missilecross = "When projectile crosses"; + impact = "On projectile impact"; +} + linedefrenderstyles { translucent = "Translucent"; @@ -610,6 +625,13 @@ script = This lump is a text-based script. Specify the filename of the script co */ udmfmaplumpnames { + BEHAVIOR + { + required = false; + nodebuild = false; + blindcopy = true; + } + ZNODES { required = false; diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 20caecf7bd6a65a20631318799016da319d3f13d..e96532d044fa3e5a7ea3b94be0d682928f4211e5 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -150,6 +150,7 @@ set(SRB2_CONFIG_DEV_BUILD OFF CACHE BOOL "Compile a development build of SRB2.") add_subdirectory(blua) +add_subdirectory(acs) add_subdirectory(netcode) # OS macros @@ -326,7 +327,6 @@ target_compile_options(SRB2SDL2 PRIVATE -Wno-absolute-value -Wextra -Wno-trigraphs - -Wconditional-uninitialized -Wno-error=non-literal-null-conversion -Wno-error=constant-conversion -Wno-error=unused-but-set-variable @@ -345,7 +345,6 @@ target_compile_options(SRB2SDL2 PRIVATE -Wall -Wextra -Wno-trigraphs - -Wconditional-uninitialized > # C++, MSVC diff --git a/src/Makefile b/src/Makefile index 40037834d3b2831f92a9897ea51336060addfc24..aa79ad137064a29c108ff6d2033a926a0afe26d2 100644 --- a/src/Makefile +++ b/src/Makefile @@ -183,10 +183,11 @@ objdir:=$(makedir)/objs sources+=\ $(call List,Sourcefile)\ $(call List,blua/Sourcefile)\ + $(call List,acs/Sourcefile)\ $(call List,netcode/Sourcefile)\ -depends:=$(basename $(filter %.c %.s,$(sources))) -objects:=$(basename $(filter %.c %.s %.nas,$(sources))) +depends:=$(basename $(filter %.c %.cpp,$(sources))) +objects:=$(basename $(filter %.c %.cpp,$(sources))) depends:=$(depends:%=$(depdir)/%.d) @@ -251,7 +252,6 @@ opts+=$(foreach v,$(passthru_opts),$(if $($(v)),-D$(v))) opts+=$(WFLAGS) $(CPPFLAGS) $(CFLAGS) libs+=$(LDFLAGS) -asflags:=$(ASFLAGS) -x assembler-with-cpp cc=$(CC) @@ -355,7 +355,7 @@ endif endef $(eval $(call _recipe,c)) -$(eval $(call _recipe,s,$(asflags))) +$(eval $(call _recipe,cpp)) # compiling recipe template # 1: target file suffix @@ -368,7 +368,7 @@ $(objdir)/%.$(1) : %.$(2) | $$$$(@D)/ endef $(eval $(call _recipe,o,c,$(cc) -c -o $$@ $$<)) -$(eval $(call _recipe,o,s,$(cc) $(asflags) -c -o $$@ $$<)) +$(eval $(call _recipe,o,cpp,$(cc) -c -o $$@ $$<)) $(eval $(call _recipe,res,rc,$(windres) -i $$< -o $$@)) _rm=$(.)$(rmrf) $(call Windows_path,$(1)) diff --git a/src/Makefile.d/platform.mk b/src/Makefile.d/platform.mk index d9a2954f60c54b30c121c0467022c1707f720fc0..b59214e9f6273f77835a39fe9c3dbf41f4fd2139 100644 --- a/src/Makefile.d/platform.mk +++ b/src/Makefile.d/platform.mk @@ -43,7 +43,6 @@ platform=cygwin else ifdef MINGW ifdef MINGW64 NONX86=1 -NOASM=1 # MINGW64 should not necessarily imply X86_64=1, # but we make that assumption elsewhere # Once that changes, remove this diff --git a/src/Makefile.d/versions.mk b/src/Makefile.d/versions.mk index 7c130d90846ab6f4199eb618ee918fdb6999e5cc..7dee2b657a06fefcc41d4a57cc5ab38edcdd3569 100644 --- a/src/Makefile.d/versions.mk +++ b/src/Makefile.d/versions.mk @@ -38,7 +38,8 @@ ifdef GCC41 WFLAGS+=-Wshadow endif #WFLAGS+=-Wlarger-than-%len% - WFLAGS+=-Wpointer-arith -Wbad-function-cast + WFLAGS+=-Wpointer-arith +#WFLAGS+=-Wbad-function-cast ifdef GCC45 #WFLAGS+=-Wc++-compat endif @@ -68,9 +69,10 @@ endif endif #WFLAGS+=-Wstrict-prototypes ifdef GCC40 - WFLAGS+=-Wold-style-definition +#WFLAGS+=-Wold-style-definition endif - WFLAGS+=-Wmissing-prototypes -Wmissing-declarations + WFLAGS+=-Wmissing-declarations +#WFLAGS+=-Wmissing-prototypes ifdef GCC40 WFLAGS+=-Wmissing-field-initializers endif @@ -81,7 +83,7 @@ endif #WFLAGS+=-Wpacked #WFLAGS+=-Wpadded #WFLAGS+=-Wredundant-decls - WFLAGS+=-Wnested-externs +#WFLAGS+=-Wnested-externs #WFLAGS+=-Wunreachable-code WFLAGS+=-Winline ifdef DEBUGMODE diff --git a/src/acs/CMakeLists.txt b/src/acs/CMakeLists.txt new file mode 100644 index 0000000000000000000000000000000000000000..ccf5fafee99657a58241896fe1838e1497459d44 --- /dev/null +++ b/src/acs/CMakeLists.txt @@ -0,0 +1,24 @@ +target_sources(SRB2SDL2 PRIVATE + environment.cpp + environment.hpp + thread.cpp + thread.hpp + call-funcs.cpp + call-funcs.hpp + stream.cpp + stream.hpp + interface.cpp + interface.h +) + +target_include_directories(SRB2SDL2 PRIVATE vm) # This sucks + +# This breaks Apple Clang 14 compile. It should be totally +# unecessary since even though vm/CMakeLists.txt sets +# CMAKE_CXX_FLAGS, it is in a lower scope. +#set(ACSVM_NOFLAGS ON) + +set(ACSVM_SHARED OFF) +add_subdirectory(vm) + +target_link_libraries(SRB2SDL2 PRIVATE acsvm) diff --git a/src/acs/Sourcefile b/src/acs/Sourcefile new file mode 100644 index 0000000000000000000000000000000000000000..cbebfdb9dd4d0718608af31981c127567193c881 --- /dev/null +++ b/src/acs/Sourcefile @@ -0,0 +1,26 @@ +environment.cpp +thread.cpp +call-funcs.cpp +stream.cpp +interface.cpp +vm/ACSVM/Action.cpp +vm/ACSVM/Array.cpp +vm/ACSVM/BinaryIO.cpp +vm/ACSVM/CallFunc.cpp +vm/ACSVM/CodeData.cpp +vm/ACSVM/Environment.cpp +vm/ACSVM/Error.cpp +vm/ACSVM/Function.cpp +vm/ACSVM/Init.cpp +vm/ACSVM/Jump.cpp +vm/ACSVM/Module.cpp +vm/ACSVM/ModuleACS0.cpp +vm/ACSVM/ModuleACSE.cpp +vm/ACSVM/PrintBuf.cpp +vm/ACSVM/Scope.cpp +vm/ACSVM/Script.cpp +vm/ACSVM/Serial.cpp +vm/ACSVM/String.cpp +vm/ACSVM/Thread.cpp +vm/ACSVM/ThreadExec.cpp +vm/ACSVM/Tracer.cpp diff --git a/src/acs/acsvm.hpp b/src/acs/acsvm.hpp new file mode 100644 index 0000000000000000000000000000000000000000..849dca0aa852b58c3f18c74b5ac279a013757dc5 --- /dev/null +++ b/src/acs/acsvm.hpp @@ -0,0 +1,50 @@ +// SONIC ROBO BLAST 2 +//----------------------------------------------------------------------------- +// Copyright (C) 2024 by Russell's Smart Interfaces +// Copyright (C) 2024 by Sonic Team Junior. +// Copyright (C) 2016 by James Haley, David Hill, et al. (Team Eternity) +// Copyright (C) 2024 by Sally "TehRealSalt" Cochenour +// Copyright (C) 2024 by Kart Krew +// +// This program is free software distributed under the +// terms of the GNU General Public License, version 2. +// See the 'LICENSE' file for more details. +//----------------------------------------------------------------------------- +/// \file acsvm.hpp +/// \brief ACSVM include file + +#ifndef __SRB2_ACSVM_HPP__ +#define __SRB2_ACSVM_HPP__ + +#include <ACSVM/Action.hpp> +#include <ACSVM/Array.hpp> +#include <ACSVM/BinaryIO.hpp> +#include <ACSVM/CallFunc.hpp> +#include <ACSVM/Code.hpp> +#include <ACSVM/CodeData.hpp> +#include <ACSVM/CodeList.hpp> +#include <ACSVM/Environment.hpp> +#include <ACSVM/Error.hpp> +#include <ACSVM/Function.hpp> +#include <ACSVM/HashMap.hpp> +#include <ACSVM/HashMapFixed.hpp> +#include <ACSVM/ID.hpp> +#include <ACSVM/Init.hpp> +#include <ACSVM/Jump.hpp> +#include <ACSVM/List.hpp> +#include <ACSVM/Module.hpp> +#include <ACSVM/PrintBuf.hpp> +#include <ACSVM/Scope.hpp> +#include <ACSVM/Script.hpp> +#include <ACSVM/Serial.hpp> +#include <ACSVM/Stack.hpp> +#include <ACSVM/Store.hpp> +#include <ACSVM/String.hpp> +#include <ACSVM/Thread.hpp> +#include <ACSVM/Tracer.hpp> +#include <ACSVM/Types.hpp> +#include <ACSVM/Vector.hpp> + +#include <Util/Floats.hpp> + +#endif //__SRB2_ACSVM_HPP__ diff --git a/src/acs/call-funcs.cpp b/src/acs/call-funcs.cpp new file mode 100644 index 0000000000000000000000000000000000000000..8418ed72b7dfc84e92470b5b9fddeb65f7b7d6cd --- /dev/null +++ b/src/acs/call-funcs.cpp @@ -0,0 +1,3463 @@ +// SONIC ROBO BLAST 2 +//----------------------------------------------------------------------------- +// Copyright (C) 2024 by Russell's Smart Interfaces +// Copyright (C) 2024 by Sonic Team Junior. +// Copyright (C) 2016 by James Haley, David Hill, et al. (Team Eternity) +// Copyright (C) 2024 by Sally "TehRealSalt" Cochenour +// Copyright (C) 2024 by Kart Krew +// +// This program is free software distributed under the +// terms of the GNU General Public License, version 2. +// See the 'LICENSE' file for more details. +//----------------------------------------------------------------------------- +/// \file call-funcs.cpp +/// \brief Action Code Script: CallFunc instructions + +#include <algorithm> +#include <cctype> + +#include "acsvm.hpp" + +#include "../doomtype.h" +#include "../doomdef.h" +#include "../doomstat.h" + +#include "../d_think.h" +#include "../p_mobj.h" +#include "../p_tick.h" +#include "../w_wad.h" +#include "../m_misc.h" +#include "../m_random.h" +#include "../g_game.h" +#include "../d_player.h" +#include "../r_defs.h" +#include "../r_state.h" +#include "../p_polyobj.h" +#include "../taglist.h" +#include "../p_local.h" +#include "../b_bot.h" +#include "../info.h" +#include "../deh_tables.h" +#include "../fastcmp.h" +#include "../hu_stuff.h" +#include "../s_sound.h" +#include "../r_textures.h" +#include "../m_cond.h" +#include "../r_skins.h" +#include "../z_zone.h" +#include "../s_sound.h" +#include "../r_draw.h" +#include "../r_fps.h" +#include "../netcode/net_command.h" + +#include "call-funcs.hpp" + +#include "environment.hpp" +#include "thread.hpp" +#include "../cxxutil.hpp" + +using namespace srb2::acs; + +/*-------------------------------------------------- + static bool ACS_GetMobjTypeFromString(const char *word, mobjtype_t *type) + + Helper function for CallFunc_ThingCount. Gets + an object type from a string. + + Input Arguments:- + word: The mobj class string. + type: Variable to store the result in. + + Return:- + true if successful, otherwise false. +--------------------------------------------------*/ +static bool ACS_GetMobjTypeFromString(const char *word, mobjtype_t *type) +{ + if (fastncmp("MT_", word, 3)) + { + // take off the MT_ + word += 3; + } + + for (int i = 0; i < NUMMOBJFREESLOTS; i++) + { + if (!FREE_MOBJS[i]) + { + break; + } + + if (fastcmp(word, FREE_MOBJS[i])) + { + *type = static_cast<mobjtype_t>(static_cast<int>(MT_FIRSTFREESLOT) + i); + return true; + } + } + + for (int i = 0; i < MT_FIRSTFREESLOT; i++) + { + if (fastcmp(word, MOBJTYPE_LIST[i] + 3)) + { + *type = static_cast<mobjtype_t>(i); + return true; + } + } + + return false; +} + +/*-------------------------------------------------- + static bool ACS_GetSFXFromString(const char *word, sfxenum_t *type) + + Helper function for sound playing functions. + Gets a SFX id from a string. + + Input Arguments:- + word: The sound effect string. + type: Variable to store the result in. + + Return:- + true if successful, otherwise false. +--------------------------------------------------*/ +static bool ACS_GetSFXFromString(const char *word, sfxenum_t *type) +{ + if (fastnicmp("SFX_", word, 4)) // made case insensitive + { + // take off the SFX_ + word += 4; + } + else if (fastnicmp("DS", word, 2)) // made case insensitive + { + // take off the DS + word += 2; + } + + for (int i = 0; i < NUMSFX; i++) + { + if (S_sfx[i].name && fasticmp(word, S_sfx[i].name)) + { + *type = static_cast<sfxenum_t>(i); + return true; + } + } + + return false; +} + +/*-------------------------------------------------- + static bool ACS_GetSpriteFromString(const char *word, spritenum_t *type) + + Helper function for CallFunc_Get/SetThingProperty. + Gets a sprite from a string. + + Input Arguments:- + word: The sprite string. + type: Variable to store the result in. + + Return:- + true if successful, otherwise false. +--------------------------------------------------*/ +static bool ACS_GetSpriteFromString(const char *word, spritenum_t *type) +{ + if (fastncmp("SPR_", word, 4)) + { + // take off the SPR_ + word += 4; + } + + for (int i = 0; i < NUMSPRITES; i++) + { + if (fastncmp(word, sprnames[i], 4)) + { + *type = static_cast<spritenum_t>(i); + return true; + } + } + + return false; +} + +/*-------------------------------------------------- + static bool ACS_GetSprite2FromString(const char *word, playersprite_t *type) + + Helper function for CallFunc_Get/SetThingProperty. + Gets a sprite2 from a string. + + Input Arguments:- + word: The sprite2 string. + type: Variable to store the result in. + + Return:- + true if successful, otherwise false. +--------------------------------------------------*/ +static bool ACS_GetSprite2FromString(const char *word, playersprite_t *type) +{ + if (fastncmp("SPR2_", word, 5)) + { + // take off the SPR2_ + word += 5; + } + + for (int i = 0; i < free_spr2; i++) + { + if (fastcmp(word, spr2names[i])) + { + *type = static_cast<playersprite_t>(i); + return true; + } + } + + return false; +} + +/*-------------------------------------------------- + static bool ACS_GetStateFromString(const char *word, playersprite_t *type) + + Helper function for CallFunc_Get/SetThingProperty. + Gets a state from a string. + + Input Arguments:- + word: The state string. + type: Variable to store the result in. + + Return:- + true if successful, otherwise false. +--------------------------------------------------*/ +static bool ACS_GetStateFromString(const char *word, statenum_t *type) +{ + if (fastncmp("S_", word, 2)) + { + // take off the S_ + word += 2; + } + + for (int i = 0; i < NUMMOBJFREESLOTS; i++) + { + if (!FREE_STATES[i]) + { + break; + } + + if (fastcmp(word, FREE_STATES[i])) + { + *type = static_cast<statenum_t>(static_cast<int>(S_FIRSTFREESLOT) + i); + return true; + } + } + + for (int i = 0; i < S_FIRSTFREESLOT; i++) + { + if (fastcmp(word, STATE_LIST[i] + 2)) + { + *type = static_cast<statenum_t>(i); + return true; + } + } + + return false; +} + +/*-------------------------------------------------- + static bool ACS_GetSkinFromString(const char *word, INT32 *type) + + Helper function for CallFunc_Get/SetThingProperty. + Gets a skin from a string. + + Input Arguments:- + word: The skin string. + type: Variable to store the result in. + + Return:- + true if successful, otherwise false. +--------------------------------------------------*/ +static bool ACS_GetSkinFromString(const char *word, INT32 *type) +{ + INT32 skin = R_SkinAvailable(word); + if (skin == -1) + return false; + + *type = skin; + return true; +} + +/*-------------------------------------------------- + static bool ACS_GetColorFromString(const char *word, skincolornum_t *type) + + Helper function for CallFunc_Get/SetThingProperty. + Gets a color from a string. + + Input Arguments:- + word: The color string. + type: Variable to store the result in. + + Return:- + true if successful, otherwise false. +--------------------------------------------------*/ +static bool ACS_GetColorFromString(const char *word, skincolornum_t *type) +{ + for (int i = 0; i < numskincolors; i++) + { + if (fastcmp(word, skincolors[i].name)) + { + *type = static_cast<skincolornum_t>(i); + return true; + } + } + + return false; +} + +/*-------------------------------------------------- + static bool ACS_CountThing(mobj_t *mobj, mobjtype_t type) + + Helper function for CallFunc_ThingCount. + Returns whenever or not to add this thing + to the thing count. + + Input Arguments:- + mobj: The mobj we want to count. + type: Type exclusion. + + Return:- + true if successful, otherwise false. +--------------------------------------------------*/ +static bool ACS_CountThing(mobj_t *mobj, mobjtype_t type) +{ + if (type == MT_NULL || mobj->type == type) + { + // Don't count dead monsters + if (mobj->info->spawnhealth > 0 && mobj->health <= 0) + { + // Note: Hexen checks for COUNTKILL. + // SRB2 does not have an equivalent, so I'm checking + // spawnhealth. Feel free to replace this condition + // with literally anything else. + return false; + } + + // Count this object. + return true; + } + + return false; +} + +/*-------------------------------------------------- + static bool ACS_ActivatorIsLocal(ACSVM::Thread *thread) + + Helper function for many print functions. + Returns whenever or not the activator of the + thread is a display player or not. + + Input Arguments:- + thread: The thread we're exeucting on. + + Return:- + true if it's for a display player, + otherwise false. +--------------------------------------------------*/ +static bool ACS_ActivatorIsLocal(ACSVM::Thread *thread) +{ + auto info = &static_cast<Thread *>(thread)->info; + + if ((info != NULL) + && (info->mo != NULL && P_MobjWasRemoved(info->mo) == false) + && (info->mo->player != NULL)) + { + return info->mo->player == &players[displayplayer]; + } + + return false; +} + +/*-------------------------------------------------- + static UINT32 ACS_SectorThingCounter(sector_t *sec, mtag_t thingTag, bool (*filter)(mobj_t *)) + + Helper function for CallFunc_CountEnemies + and CallFunc_CountPushables. Counts a number + of things in the specified sector. + + Input Arguments:- + sec: The sector to search in. + thingTag: Thing tag to filter for. 0 allows any. + filter: Filter function, total count is increased when + this function returns true. + + Return:- + Numbers of things matching the filter found. +--------------------------------------------------*/ +static UINT32 ACS_SectorThingCounter(sector_t *sec, mtag_t thingTag, bool (*filter)(mobj_t *)) +{ + UINT32 count = 0; + + for (msecnode_t *node = sec->touching_thinglist; node; node = node->m_thinglist_next) // things touching this sector + { + mobj_t *mo = node->m_thing; + + if (thingTag != 0 && mo->tid != thingTag) + { + continue; + } + + if (mo->z > sec->ceilingheight + || mo->z + mo->height < sec->floorheight) + { + continue; + } + + if (filter(mo) == true) + { + count++; + } + } + + return count; +} + +/*-------------------------------------------------- + static UINT32 ACS_SectorTagThingCounter(mtag_t sectorTag, sector_t *activator, mtag_t thingTag, bool (*filter)(mobj_t *)) + + Helper function for CallFunc_CountEnemies + and CallFunc_CountPushables. Counts a number + of things in the tagged sectors. + + Input Arguments:- + sectorTag: The sector tag to search in. + activator: The activator sector to fall back on when sectorTag is 0. + thingTag: Thing tag to filter for. 0 allows any. + filter: Filter function, total count is increased when + this function returns true. + + Return:- + Numbers of things matching the filter found. +--------------------------------------------------*/ +static UINT32 ACS_SectorIterateThingCounter(sector_t *sec, mtag_t thingTag, bool (*filter)(mobj_t *)) +{ + UINT32 count = 0; + boolean FOFsector = false; + size_t i; + + if (sec == nullptr) + { + return 0; + } + + // Check the lines of this sector, to see if it is a FOF control sector. + for (i = 0; i < sec->linecount; i++) + { + INT32 targetsecnum = -1; + + if (sec->lines[i]->special < 100 || sec->lines[i]->special >= 300) + { + continue; + } + + FOFsector = true; + + TAG_ITER_SECTORS(sec->lines[i]->args[0], targetsecnum) + { + sector_t *targetsec = §ors[targetsecnum]; + count += ACS_SectorThingCounter(targetsec, thingTag, filter); + } + } + + if (FOFsector == false) + { + count += ACS_SectorThingCounter(sec, thingTag, filter); + } + + return count; +} + +static UINT32 ACS_SectorTagThingCounter(mtag_t sectorTag, sector_t *activator, mtag_t thingTag, bool (*filter)(mobj_t *)) +{ + UINT32 count = 0; + + if (sectorTag == 0) + { + count += ACS_SectorIterateThingCounter(activator, thingTag, filter); + } + else + { + INT32 secnum = -1; + + TAG_ITER_SECTORS(sectorTag, secnum) + { + sector_t *sec = §ors[secnum]; + count += ACS_SectorIterateThingCounter(sec, thingTag, filter); + } + } + + return count; +} + +/*-------------------------------------------------- + bool CallFunc_Random(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC) + + ACS wrapper for P_RandomRange. +--------------------------------------------------*/ +bool CallFunc_Random(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC) +{ + INT32 low = 0; + INT32 high = 0; + + (void)argC; + + low = argV[0]; + high = argV[1]; + + thread->dataStk.push(P_RandomRange(low, high)); + return false; +} + +/*-------------------------------------------------- + bool CallFunc_ThingCount(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC) + + Counts the number of things of a particular + type and tid. Both fields are optional; + no type means indescriminate against type, + no tid means search thru all thinkers. +--------------------------------------------------*/ +static mobjtype_t filter_for_mobjtype = MT_NULL; // annoying but I don't wanna mess with other code +bool ACS_ThingTypeFilter(mobj_t *mo) +{ + return (ACS_CountThing(mo, filter_for_mobjtype)); +} + +bool CallFunc_ThingCount(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC) +{ + ACSVM::MapScope *map = NULL; + ACSVM::String *str = NULL; + const char *className = NULL; + size_t classLen = 0; + + mobjtype_t type = MT_NULL; + mtag_t tid = 0; + mtag_t sectorTag = 0; + + size_t count = 0; + + map = thread->scopeMap; + str = map->getString(argV[0]); + + className = str->str; + classLen = str->len; + + if (classLen > 0) + { + bool success = ACS_GetMobjTypeFromString(className, &type); + + if (success == false) + { + // Exit early. + + CONS_Alert(CONS_WARNING, + "Couldn't find object type \"%s\" for ThingCount.\n", + className + ); + + return false; + } + } + + tid = argV[1]; + + if (argC > 2) + { + sectorTag = argV[2]; + } + + if (sectorTag != 0) + { + // Search through sectors. + filter_for_mobjtype = type; + count = ACS_SectorTagThingCounter(sectorTag, nullptr, tid, ACS_ThingTypeFilter); + filter_for_mobjtype = MT_NULL; + } + else if (tid != 0) + { + // Search through tag lists. + mobj_t *mobj = nullptr; + + while ((mobj = P_FindMobjFromTID(tid, mobj, nullptr)) != nullptr) + { + if (ACS_CountThing(mobj, type) == true) + { + ++count; + } + } + } + else + { + // Search thinkers instead of tag lists. + thinker_t *th = nullptr; + mobj_t *mobj = nullptr; + + for (th = thlist[THINK_MOBJ].next; th != &thlist[THINK_MOBJ]; th = th->next) + { + if (th->function.acp1 == (actionf_p1)P_RemoveThinkerDelayed) + { + continue; + } + + mobj = (mobj_t *)th; + + if (ACS_CountThing(mobj, type) == true) + { + ++count; + } + } + } + + thread->dataStk.push(count); + return false; +} + +/*-------------------------------------------------- + bool CallFunc_TagWait(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC) + + Pauses the thread until the tagged + sector stops moving. +--------------------------------------------------*/ +bool CallFunc_TagWait(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC) +{ + (void)argC; + + thread->state = { + ACSVM::ThreadState::WaitTag, + argV[0], + ACS_TAGTYPE_SECTOR + }; + + return true; // Execution interrupted +} + +/*-------------------------------------------------- + bool CallFunc_PolyWait(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC) + + Pauses the thread until the tagged + polyobject stops moving. +--------------------------------------------------*/ +bool CallFunc_PolyWait(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC) +{ + (void)argC; + + thread->state = { + ACSVM::ThreadState::WaitTag, + argV[0], + ACS_TAGTYPE_POLYOBJ + }; + + return true; // Execution interrupted +} + +/*-------------------------------------------------- + bool CallFunc_CameraWait(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC) + + Pauses the thread until the tagged + camera is done moving. +--------------------------------------------------*/ +bool CallFunc_CameraWait(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC) +{ + (void)argC; + + thread->state = { + ACSVM::ThreadState::WaitTag, + argV[0], + ACS_TAGTYPE_CAMERA + }; + + thread->dataStk.push(0); + + return true; // Execution interrupted +} + +/*-------------------------------------------------- + bool CallFunc_ChangeFloor(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC) + + Changes a floor texture. +--------------------------------------------------*/ +bool CallFunc_ChangeFloor(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC) +{ + ACSVM::MapScope *map = nullptr; + ACSVM::String *str = nullptr; + const char *texName = nullptr; + + INT32 secnum = -1; + mtag_t tag = 0; + + (void)argC; + + tag = argV[0]; + + map = thread->scopeMap; + str = map->getString(argV[1]); + texName = str->str; + + TAG_ITER_SECTORS(tag, secnum) + { + sector_t *sec = §ors[secnum]; + sec->floorpic = P_AddLevelFlatRuntime(texName); + } + + return false; +} + +/*-------------------------------------------------- + bool CallFunc_ChangeCeiling(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC) + + Changes a ceiling texture. +--------------------------------------------------*/ +bool CallFunc_ChangeCeiling(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC) +{ + ACSVM::MapScope *map = NULL; + ACSVM::String *str = NULL; + const char *texName = NULL; + + INT32 secnum = -1; + mtag_t tag = 0; + + (void)argC; + + tag = argV[0]; + + map = thread->scopeMap; + str = map->getString(argV[1]); + texName = str->str; + + TAG_ITER_SECTORS(tag, secnum) + { + sector_t *sec = §ors[secnum]; + sec->ceilingpic = P_AddLevelFlatRuntime(texName); + } + + return false; +} + +/*-------------------------------------------------- + bool CallFunc_LineSide(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC) + + Pushes which side of the linedef was + activated. +--------------------------------------------------*/ +bool CallFunc_LineSide(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC) +{ + auto info = &static_cast<Thread *>(thread)->info; + + (void)argV; + (void)argC; + + thread->dataStk.push(info->side); + return false; +} + +/*-------------------------------------------------- + bool CallFunc_ClearLineSpecial(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC) + + If there is an activating linedef, set its + special to 0. +--------------------------------------------------*/ +bool CallFunc_ClearLineSpecial(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC) +{ + auto info = &static_cast<Thread *>(thread)->info; + + (void)argV; + (void)argC; + + if (info->line != NULL) + { + // One time only. + info->line->special = 0; + } + + return false; +} + +/*-------------------------------------------------- + bool CallFunc_EndPrint(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC) + + One of the ACS wrappers for CEcho. This + version only prints if the activator is a + display player. +--------------------------------------------------*/ +bool CallFunc_EndPrint(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC) +{ + (void)argV; + (void)argC; + + auto& info = static_cast<Thread*>(thread)->info; + + if (P_MobjWasRemoved(info.mo) == false && info.mo->player != nullptr) + { + if (ACS_ActivatorIsLocal(thread)) + HU_DoCEcho(thread->printBuf.data()); + } + + thread->printBuf.drop(); + return false; +} + +/*-------------------------------------------------- + bool CallFunc_PlayerCount(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC) + + Pushes the number of players to ACS. +--------------------------------------------------*/ +bool CallFunc_PlayerCount(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC) +{ + UINT8 numPlayers = 0; + UINT8 i; + + (void)argV; + (void)argC; + + for (i = 0; i < MAXPLAYERS; i++) + { + player_t *player = NULL; + + if (playeringame[i] == false) + { + continue; + } + + player = &players[i]; + + if (player->spectator == true) + { + continue; + } + + numPlayers++; + } + + thread->dataStk.push(numPlayers); + return false; +} + +/*-------------------------------------------------- + bool CallFunc_GameType(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC) + + Pushes the current gametype to ACS. +--------------------------------------------------*/ +bool CallFunc_GameType(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC) +{ + (void)argV; + (void)argC; + + thread->dataStk.push(gametype); + return false; +} + +/*-------------------------------------------------- + bool CallFunc_Timer(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC) + + Pushes leveltime to ACS. +--------------------------------------------------*/ +bool CallFunc_Timer(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC) +{ + (void)argV; + (void)argC; + + thread->dataStk.push(leveltime); + return false; +} + +/*-------------------------------------------------- + bool CallFunc_IsNetworkGame(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC) + + Pushes netgame status to ACS. +--------------------------------------------------*/ +bool CallFunc_IsNetworkGame(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC) +{ + (void)argV; + (void)argC; + + thread->dataStk.push(netgame); + return false; +} + +/*-------------------------------------------------- + bool CallFunc_SectorSound(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC) + + Plays a point sound effect from a sector. +--------------------------------------------------*/ +bool CallFunc_SectorSound(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC) +{ + auto info = &static_cast<Thread *>(thread)->info; + + ACSVM::MapScope *map = nullptr; + ACSVM::String *str = nullptr; + + const char *sfxName = nullptr; + size_t sfxLen = 0; + + sfxenum_t sfxId = sfx_None; + INT32 vol = 0; + mobj_t *origin = nullptr; + + (void)argC; + + map = thread->scopeMap; + str = map->getString(argV[0]); + + sfxName = str->str; + sfxLen = str->len; + + if (sfxLen > 0) + { + bool success = ACS_GetSFXFromString(sfxName, &sfxId); + + if (success == false) + { + // Exit early. + + CONS_Alert(CONS_WARNING, + "Couldn't find sfx named \"%s\" for SectorSound.\n", + sfxName + ); + + return false; + } + } + + vol = argV[1]; + + if (info->sector != nullptr) + { + // New to Ring Racers: Use activating sector directly. + origin = static_cast<mobj_t *>(static_cast<void *>(&info->sector->soundorg)); + } + else if (info->line != nullptr) + { + // Original Hexen behavior: Use line's frontsector. + origin = static_cast<mobj_t *>(static_cast<void *>(&info->line->frontsector->soundorg)); + } + + S_StartSoundAtVolume(origin, sfxId, vol); + return false; +} + +/*-------------------------------------------------- + bool CallFunc_AmbientSound(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC) + + Plays a sound effect globally. +--------------------------------------------------*/ +bool CallFunc_AmbientSound(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC) +{ + ACSVM::MapScope *map = nullptr; + ACSVM::String *str = nullptr; + + const char *sfxName = nullptr; + size_t sfxLen = 0; + + sfxenum_t sfxId = sfx_None; + INT32 vol = 0; + + (void)argC; + + map = thread->scopeMap; + str = map->getString(argV[0]); + + sfxName = str->str; + sfxLen = str->len; + + if (sfxLen > 0) + { + bool success = ACS_GetSFXFromString(sfxName, &sfxId); + + if (success == false) + { + // Exit early. + + CONS_Alert(CONS_WARNING, + "Couldn't find sfx named \"%s\" for AmbientSound.\n", + sfxName + ); + + return false; + } + } + + vol = argV[1]; + + S_StartSoundAtVolume(NULL, sfxId, vol); + return false; +} + +/*-------------------------------------------------- + bool CallFunc_SetLineTexture(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC) + + Plays a sound effect globally. +--------------------------------------------------*/ +enum +{ + SLT_POS_TOP, + SLT_POS_MIDDLE, + SLT_POS_BOTTOM +}; + +bool CallFunc_SetLineTexture(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC) +{ + mtag_t tag = 0; + UINT8 sideId = 0; + UINT8 texPos = 0; + + ACSVM::MapScope *map = NULL; + ACSVM::String *str = NULL; + const char *texName = NULL; + INT32 texId = LUMPERROR; + + INT32 lineId = -1; + + (void)argC; + + tag = argV[0]; + sideId = (argV[1] & 1); + texPos = argV[2]; + + map = thread->scopeMap; + str = map->getString(argV[3]); + texName = str->str; + + texId = R_TextureNumForName(texName); + + TAG_ITER_LINES(tag, lineId) + { + line_t *line = &lines[lineId]; + side_t *side = NULL; + + if (line->sidenum[sideId] != 0xffff) + { + side = &sides[line->sidenum[sideId]]; + } + + if (side == NULL) + { + continue; + } + + switch (texPos) + { + case SLT_POS_MIDDLE: + { + side->midtexture = texId; + break; + } + case SLT_POS_BOTTOM: + { + side->bottomtexture = texId; + break; + } + case SLT_POS_TOP: + default: + { + side->toptexture = texId; + break; + } + } + } + + return false; +} + +/*-------------------------------------------------- + bool CallFunc_SetLineSpecial(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC) + + Changes a linedef's special and arguments. +--------------------------------------------------*/ +bool CallFunc_SetLineSpecial(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC) +{ + auto info = &static_cast<Thread *>(thread)->info; + + mtag_t tag = 0; + INT32 spec = 0; + size_t numArgs = 0; + + INT32 lineId = -1; + + tag = argV[0]; + spec = argV[1]; + + numArgs = std::min(std::max((signed)(argC - 2), 0), NUM_SCRIPT_ARGS); + + TAG_ITER_LINES(tag, lineId) + { + line_t *line = &lines[lineId]; + size_t i; + + if (info->line != nullptr && line == info->line) + { + continue; + } + + line->special = spec; + + for (i = 0; i < numArgs; i++) + { + line->args[i] = argV[i + 2]; + } + } + + return false; +} + +/*-------------------------------------------------- + bool CallFunc_ChangeSky(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC) + + Changes the map's sky texture. +--------------------------------------------------*/ +bool CallFunc_ChangeSky(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC) +{ + (void)thread; + (void)argC; + + P_SetupLevelSky(argV[0], argV[1]); + + return false; +} + +/*-------------------------------------------------- + bool CallFunc_ThingSound(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC) + + Plays a sound effect for a tagged object. +--------------------------------------------------*/ +bool CallFunc_ThingSound(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC) +{ + auto info = &static_cast<Thread *>(thread)->info; + + ACSVM::MapScope *map = nullptr; + ACSVM::String *str = nullptr; + + const char *sfxName = nullptr; + size_t sfxLen = 0; + + mtag_t tag = 0; + sfxenum_t sfxId = sfx_None; + INT32 vol = 0; + + mobj_t *mobj = nullptr; + + (void)argC; + + tag = argV[0]; + + map = thread->scopeMap; + str = map->getString(argV[1]); + + sfxName = str->str; + sfxLen = str->len; + + if (sfxLen > 0) + { + bool success = ACS_GetSFXFromString(sfxName, &sfxId); + + if (success == false) + { + // Exit early. + + CONS_Alert(CONS_WARNING, + "Couldn't find sfx named \"%s\" for AmbientSound.\n", + sfxName + ); + + return false; + } + } + + vol = argV[2]; + + while ((mobj = P_FindMobjFromTID(tag, mobj, info->mo)) != nullptr) + { + S_StartSoundAtVolume(mobj, sfxId, vol); + } + + return false; +} + + +/*-------------------------------------------------- + bool CallFunc_EndPrintBold(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC) + + One of the ACS wrappers for CEcho. This + version prints for all players. +--------------------------------------------------*/ +bool CallFunc_EndPrintBold(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC) +{ + (void)argV; + (void)argC; + + HU_DoCEcho(thread->printBuf.data()); + + thread->printBuf.drop(); + return false; +} +/*-------------------------------------------------- + bool CallFunc_PlayerTeam(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC) + + Returns the activating player's team ID. +--------------------------------------------------*/ +bool CallFunc_PlayerTeam(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC) +{ + auto info = &static_cast<Thread *>(thread)->info; + UINT8 teamID = 0; + + (void)argV; + (void)argC; + + if ((info != NULL) + && (info->mo != NULL && P_MobjWasRemoved(info->mo) == false) + && (info->mo->player != NULL)) + { + teamID = info->mo->player->ctfteam; + } + + thread->dataStk.push(teamID); + return false; +} + +/*-------------------------------------------------- + bool CallFunc_PlayerRings(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC) + + Returns the activating player's ring count. +--------------------------------------------------*/ +bool CallFunc_PlayerRings(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC) +{ + auto info = &static_cast<Thread *>(thread)->info; + SINT8 rings = 0; + + (void)argV; + (void)argC; + + if ((info != NULL) + && (info->mo != NULL && P_MobjWasRemoved(info->mo) == false) + && (info->mo->player != NULL)) + { + rings = info->mo->player->rings; + } + + thread->dataStk.push(rings); + return false; +} + +/*-------------------------------------------------- + bool CallFunc_PlayerScore(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC) + + Returns the activating player's score. +--------------------------------------------------*/ +bool CallFunc_PlayerScore(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC) +{ + auto info = &static_cast<Thread *>(thread)->info; + UINT32 score = 0; + + (void)argV; + (void)argC; + + if ((info != NULL) + && (info->mo != NULL && P_MobjWasRemoved(info->mo) == false) + && (info->mo->player != NULL)) + { + score = info->mo->player->score; + } + + thread->dataStk.push(score); + return false; +} + +/*-------------------------------------------------- + bool CallFunc_PlayerNumber(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC) + + Returns the activating player's ID. +--------------------------------------------------*/ +bool CallFunc_PlayerNumber(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC) +{ + auto info = &static_cast<Thread *>(thread)->info; + INT16 playerID = -1; + + (void)argV; + (void)argC; + + if ((info != NULL) + && (info->mo != NULL && P_MobjWasRemoved(info->mo) == false) + && (info->mo->player != NULL)) + { + playerID = (info->mo->player - players); + } + + thread->dataStk.push(playerID); + return false; +} + +/*-------------------------------------------------- + bool CallFunc_ActivatorTID(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC) + + Returns the activating object's TID. +--------------------------------------------------*/ +bool CallFunc_ActivatorTID(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC) +{ + auto info = &static_cast<Thread *>(thread)->info; + INT16 tid = 0; + + (void)argV; + (void)argC; + + if ((info != NULL) + && (info->mo != NULL && P_MobjWasRemoved(info->mo) == false)) + { + tid = info->mo->tid; + } + + thread->dataStk.push(tid); + return false; +} + +/*-------------------------------------------------- + bool CallFunc_EndLog(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC) + + One of the ACS wrappers for CONS_Printf. + This version only prints if the activator + is a display player. +--------------------------------------------------*/ +bool CallFunc_EndLog(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC) +{ + (void)argV; + (void)argC; + + if (ACS_ActivatorIsLocal(thread)) + CONS_Printf("%s\n", thread->printBuf.data()); + + thread->printBuf.drop(); + return false; +} + +/*-------------------------------------------------- + bool CallFunc_strcmp(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC) + + ACS wrapper for strcmp. +--------------------------------------------------*/ +static int ACS_strcmp(ACSVM::String *a, ACSVM::String *b) +{ + for (char const *sA = a->str, *sB = b->str; ; ++sA, ++sB) + { + char cA = *sA, cB = *sB; + + if (cA != cB) + { + return (cA < cB) ? -1 : 1; + } + + if (!cA) + { + return 0; + } + } +} + +bool CallFunc_strcmp(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC) +{ + ACSVM::MapScope *map = NULL; + + ACSVM::String *strA = nullptr; + ACSVM::String *strB = nullptr; + + (void)argC; + + map = thread->scopeMap; + + strA = map->getString(argV[0]); + strB = map->getString(argV[1]); + + thread->dataStk.push(ACS_strcmp(strA, strB)); + return false; +} + +/*-------------------------------------------------- + bool CallFunc_strcasecmp(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC) + + ACS wrapper for strcasecmp / stricmp. +--------------------------------------------------*/ +static int ACS_strcasecmp(ACSVM::String *a, ACSVM::String *b) +{ + for (char const *sA = a->str, *sB = b->str; ; ++sA, ++sB) + { + char cA = std::tolower(*sA), cB = std::tolower(*sB); + + if (cA != cB) + { + return (cA < cB) ? -1 : 1; + } + + if (!cA) + { + return 0; + } + } +} + +bool CallFunc_strcasecmp(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC) +{ + ACSVM::MapScope *map = NULL; + + ACSVM::String *strA = nullptr; + ACSVM::String *strB = nullptr; + + (void)argC; + + map = thread->scopeMap; + + strA = map->getString(argV[0]); + strB = map->getString(argV[1]); + + thread->dataStk.push(ACS_strcasecmp(strA, strB)); + return false; +} + +/*-------------------------------------------------- + bool CallFunc_CountEnemies(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC) + + Returns the number of enemies in the tagged sectors. +--------------------------------------------------*/ +bool ACS_EnemyFilter(mobj_t *mo) +{ + return ((mo->flags & (MF_ENEMY|MF_BOSS)) && mo->health > 0); +} + +bool CallFunc_CountEnemies(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC) +{ + auto info = &static_cast<Thread *>(thread)->info; + + mtag_t tag = 0; + mtag_t tid = 0; + UINT32 count = 0; + + (void)argC; + + tag = argV[0]; + tid = argV[1]; + count = ACS_SectorTagThingCounter(tag, info->sector, tid, ACS_EnemyFilter); + + thread->dataStk.push(count); + return false; +} + +/*-------------------------------------------------- + bool CallFunc_CountPushables(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC) + + Returns the number of pushables in the tagged sectors. +--------------------------------------------------*/ +bool ACS_PushableFilter(mobj_t *mo) +{ + return ((mo->flags & MF_PUSHABLE) + || ((mo->info->flags & MF_PUSHABLE) && mo->fuse)); +} + +bool CallFunc_CountPushables(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC) +{ + auto info = &static_cast<Thread *>(thread)->info; + + mtag_t tag = 0; + mtag_t tid = 0; + UINT32 count = 0; + + (void)argC; + + tag = argV[0]; + tid = argV[1]; + count = ACS_SectorTagThingCounter(tag, info->sector, tid, ACS_PushableFilter); + + thread->dataStk.push(count); + return false; +} + +/*-------------------------------------------------- + bool CallFunc_HaveUnlockable(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC) + + Returns if an unlockable has been gotten. +--------------------------------------------------*/ +bool CallFunc_HaveUnlockable(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC) +{ + UINT32 id = 0; + bool unlocked = false; + + (void)argC; + + id = argV[0]; + + if (id >= MAXUNLOCKABLES) + { + CONS_Printf("Bad unlockable ID %d\n", id); + } + else + { + unlocked = M_CheckNetUnlockByID(id); + } + + thread->dataStk.push(unlocked); + return false; +} + +/*-------------------------------------------------- + bool CallFunc_PlayerSkin(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC) + + Returns the activating player's skin name. +--------------------------------------------------*/ +bool CallFunc_PlayerSkin(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC) +{ + Environment *env = &ACSEnv; + auto info = &static_cast<Thread *>(thread)->info; + + (void)argV; + (void)argC; + + if ((info != NULL) + && (info->mo != NULL && P_MobjWasRemoved(info->mo) == false) + && (info->mo->player != NULL)) + { + UINT8 skin = info->mo->player->skin; + thread->dataStk.push(~env->getString( skins[skin]->name )->idx); + return false; + } + + thread->dataStk.push(0); + return false; +} + +/*-------------------------------------------------- + bool CallFunc_PlayerBot(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC) + + Returns the activating player's bot status. +--------------------------------------------------*/ +bool CallFunc_PlayerBot(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC) +{ + auto info = &static_cast<Thread *>(thread)->info; + + (void)argV; + (void)argC; + + if ((info != NULL) + && (info->mo != NULL && P_MobjWasRemoved(info->mo) == false) + && (info->mo->player != NULL)) + { + + thread->dataStk.push(info->mo->player->bot); + return false; + } + + thread->dataStk.push(false); + return false; +} + +/*-------------------------------------------------- + bool CallFunc_PlayerExiting(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC) + + Returns the activating player's exiting status. +--------------------------------------------------*/ +bool CallFunc_PlayerExiting(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC) +{ + auto info = &static_cast<Thread *>(thread)->info; + + (void)argV; + (void)argC; + + if ((info != NULL) + && (info->mo != NULL && P_MobjWasRemoved(info->mo) == false) + && (info->mo->player != NULL)) + { + thread->dataStk.push((info->mo->player->exiting != 0)); + return false; + } + + thread->dataStk.push(false); + return false; +} + +/*-------------------------------------------------- + bool CallFunc_SetObjectDye(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC) + + Dyes the activating object. +--------------------------------------------------*/ +bool CallFunc_SetObjectDye(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC) +{ + auto info = &static_cast<Thread *>(thread)->info; + + (void)argC; + + if ((info != NULL) + && (info->mo != NULL && P_MobjWasRemoved(info->mo) == false)) + { + var1 = 0; + var2 = argV[0]; + A_Dye(info->mo); + } + + thread->dataStk.push(0); + + return false; +} + +/*-------------------------------------------------- + bool CallFunc_GetObjectDye(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC) + + Returns the activating object's current dye. +--------------------------------------------------*/ +bool CallFunc_GetObjectDye(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC) +{ + Environment *env = &ACSEnv; + auto info = &static_cast<Thread *>(thread)->info; + UINT16 dye = SKINCOLOR_NONE; + + (void)argV; + (void)argC; + + if ((info != NULL) + && (info->mo != NULL && P_MobjWasRemoved(info->mo) == false)) + { + dye = (info->mo->player != NULL) ? info->mo->player->powers[pw_dye] : info->mo->color; + } + + thread->dataStk.push(~env->getString( skincolors[dye].name )->idx); + return false; +} + +/*-------------------------------------------------- + bool CallFunc_PlayerEmeralds(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC) + + Returns the activating player's number of Chaos Emeralds. +--------------------------------------------------*/ +bool CallFunc_PlayerEmeralds(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC) +{ + auto info = &static_cast<Thread *>(thread)->info; + UINT8 count = 0; + + (void)argV; + (void)argC; + + if ((info != NULL) + && (info->mo != NULL && P_MobjWasRemoved(info->mo) == false) + && (info->mo->player != NULL)) + { + UINT32 emerbits; + + if (G_PlatformGametype()) + emerbits = emeralds; + else + emerbits = info->mo->player->powers[pw_emeralds]; + + for (unsigned i = 0; i < 7; i++) + { + if (emerbits & (1 << i)) + count++; + } + } + + thread->dataStk.push(count); + return false; +} + +/*-------------------------------------------------- + bool CallFunc_PlayerLap(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC) + + Returns the activating player's current lap. +--------------------------------------------------*/ +bool CallFunc_PlayerLap(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC) +{ + auto info = &static_cast<Thread *>(thread)->info; + UINT8 laps = 0; + + (void)argV; + (void)argC; + + if ((info != NULL) + && (info->mo != NULL && P_MobjWasRemoved(info->mo) == false) + && (info->mo->player != NULL)) + { + laps = info->mo->player->laps; + } + + thread->dataStk.push(laps); + return false; +} + +/*-------------------------------------------------- + bool CallFunc_LowestLap(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC) + + Returns the lowest lap of all of the players in-game. +--------------------------------------------------*/ +bool CallFunc_LowestLap(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC) +{ + (void)argV; + (void)argC; + + thread->dataStk.push(P_FindLowestLap()); + return false; +} + +/*-------------------------------------------------- + bool CallFunc_RingslingerMode(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC) + + Returns if the map is currently in a shooting gametype. +--------------------------------------------------*/ +bool CallFunc_RingSlingerMode(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC) +{ + (void)argV; + (void)argC; + + thread->dataStk.push(G_RingSlingerGametype()); + return false; +} + +/*-------------------------------------------------- + bool CallFunc_CaptureTheFlagMode(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC) + + Returns if the map is currently in a Capture The Flag gametype. +--------------------------------------------------*/ +bool CallFunc_CaptureTheFlagMode(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC) +{ + (void)argV; + (void)argC; + + thread->dataStk.push((gametyperules & GTR_TEAMFLAGS) != 0); + return false; +} + +/*-------------------------------------------------- + bool CallFunc_TeamGame(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC) + + Returns if the map is currently in a team gametype. +--------------------------------------------------*/ +bool CallFunc_TeamGame(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC) +{ + (void)argV; + (void)argC; + + thread->dataStk.push(G_GametypeHasTeams()); + return false; +} + +/*-------------------------------------------------- + bool CallFunc_ModeAttacking(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC) + + Returns if the map is a Mode Attack session. +--------------------------------------------------*/ +bool CallFunc_ModeAttacking(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC) +{ + (void)argV; + (void)argC; + + thread->dataStk.push((modeattacking != ATTACKING_NONE)); + return false; +} + +/*-------------------------------------------------- + bool CallFunc_RecordAttack(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC) + + Returns if the map is a Record Attack session. +--------------------------------------------------*/ +bool CallFunc_RecordAttack(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC) +{ + (void)argV; + (void)argC; + + thread->dataStk.push((modeattacking == ATTACKING_RECORD)); + return false; +} + +/*-------------------------------------------------- + bool CallFunc_NiGHTSAttack(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC) + + Returns if the map is a NiGHTS Attack session. +--------------------------------------------------*/ +bool CallFunc_NiGHTSAttack(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC) +{ + (void)argV; + (void)argC; + + thread->dataStk.push((modeattacking == ATTACKING_NIGHTS)); + return false; +} + +/*-------------------------------------------------- + bool CallFunc_SetLineRenderStyle(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC) + + Changes a linedef's blend mode and alpha. +--------------------------------------------------*/ +bool CallFunc_SetLineRenderStyle(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC) +{ + mtag_t tag = 0; + patchalphastyle_t blend = AST_COPY; + fixed_t alpha = FRACUNIT; + + INT32 lineId = -1; + + (void)thread; + (void)argC; + + tag = argV[0]; + + switch (argV[1]) + { + case TMB_TRANSLUCENT: + default: + blend = AST_COPY; + break; + case TMB_ADD: + blend = AST_ADD; + break; + case TMB_SUBTRACT: + blend = AST_SUBTRACT; + break; + case TMB_REVERSESUBTRACT: + blend = AST_REVERSESUBTRACT; + break; + case TMB_MODULATE: + blend = AST_MODULATE; + break; + } + + alpha = argV[2]; + alpha = std::clamp(alpha, 0, FRACUNIT); + + TAG_ITER_LINES(tag, lineId) + { + line_t *line = &lines[lineId]; + + line->blendmode = blend; + line->alpha = alpha; + } + + thread->dataStk.push(0); + + return false; +} + +/*-------------------------------------------------- + bool CallFunc_MapWarp(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC) + + Immediately warps to another level. +--------------------------------------------------*/ + +bool CallFunc_MapWarp(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC) +{ + ACSVM::MapScope *map = NULL; + + ACSVM::String *str = nullptr; + + const char *levelName = NULL; + size_t levelLen = 0; + + INT16 nextmap = 0; + + (void)argC; + + map = thread->scopeMap; + + str = map->getString(argV[0]); + + levelName = str->str; + levelLen = str->len; + + if (!levelLen || !levelName) + { + CONS_Alert(CONS_WARNING, "MapWarp level name was not provided.\n"); + } + + if (levelName[0] == 'M' && levelName[1] == 'A' && levelName[2] == 'P' && levelName[5] == '\0') + { + nextmap = (INT16)M_MapNumber(levelName[3], levelName[4]); + } + + if (nextmap == 0) + { + CONS_Alert(CONS_WARNING, "MapWarp level %s is not valid or loaded.\n", levelName); + return false; + } + + nextmapoverride = (nextmap + 1); + + if (argV[1] == 0) + skipstats = 1; + + if (server) + SendNetXCmd(XD_EXITLEVEL, NULL, 0); + + thread->dataStk.push(0); + + return false; +} + +/*-------------------------------------------------- + bool CallFunc_AddBot(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC) + + Inserts a bot, if there's room for them. +--------------------------------------------------*/ +bool CallFunc_AddBot(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC) +{ + ACSVM::MapScope *map = thread->scopeMap; + + ACSVM::String *skinStr = nullptr; + const char *skinname = NULL; + + ACSVM::String *nameStr = nullptr; + const char *botname = NULL; + + UINT16 skincolor = SKINCOLOR_NONE; + + SINT8 bottype = BOT_MPAI; + + // Get name + skinStr = map->getString(argV[0]); + if (skinStr->len != 0) + skinname = skinStr->str; + + // Get skincolor + if (argC >= 2) + skincolor = std::clamp(static_cast<int>(argV[1]), (int)SKINCOLOR_NONE, (int)(MAXSKINCOLORS - 1)); + + // Get type + if (argC >= 3) + { + bottype = static_cast<int>(argV[2]); + if (bottype < BOT_NONE || bottype > BOT_MPAI) + { + bottype = BOT_MPAI; + } + } + + // Get name + if (argC >= 3) + { + nameStr = map->getString(argV[3]); + if (nameStr->len != 0) + { + botname = nameStr->str; + } + } + + thread->dataStk.push(B_AddBot(skinname, skincolor, botname, bottype)); + + return false; +} + +/*-------------------------------------------------- + bool CallFunc_ExitLevel(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC) + + Exits the level. +--------------------------------------------------*/ +bool CallFunc_ExitLevel(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC) +{ + (void)thread; + (void)argV; + (void)argC; + + if (argC >= 1) + { + skipstats = (argV[0] == 0); + } + + if (server) + SendNetXCmd(XD_EXITLEVEL, NULL, 0); + + return false; +} + +/*-------------------------------------------------- + bool CallFunc_MusicPlay(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC) + + Play a tune. If it's already playing, restart from the + beginning. +--------------------------------------------------*/ +bool CallFunc_MusicPlay(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC) +{ + ACSVM::MapScope *map = thread->scopeMap; + + // 0: str tune - id for the tune to play + // 1: [bool foractivator] - only do this if the activator is a player and is being viewed + + if (argC > 1 && argV[1] && !ACS_ActivatorIsLocal(thread)) + { + return false; + } + + S_StopMusic(); + S_ChangeMusicInternal(map->getString(argV[0])->str, true); + + thread->dataStk.push(0); + + return false; +} + +/*-------------------------------------------------- + bool CallFunc_MusicStopAll(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC) + + Stop every tune that is currently playing. +--------------------------------------------------*/ +bool CallFunc_MusicStopAll(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC) +{ + // 0: [bool foractivator] - only do this if the activator is a player and is being viewed + + if (argC > 0 && argV[0] && !ACS_ActivatorIsLocal(thread)) + { + return false; + } + + S_StopMusic(); + + thread->dataStk.push(0); + + return false; +} + +/*-------------------------------------------------- + bool CallFunc_MusicRestore(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC) + + Restores the map music. +--------------------------------------------------*/ +bool CallFunc_MusicRestore(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC) +{ + (void)argV; + (void)argC; + + S_ChangeMusicEx(mapmusname, mapmusflags, true, mapmusposition, 0, 0); + + thread->dataStk.push(0); + + return false; +} + +/*-------------------------------------------------- + bool CallFunc_MusicDim(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC) + + Fade level music into or out of silence. +--------------------------------------------------*/ +bool CallFunc_MusicDim(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC) +{ + (void)thread; + (void)argC; + + // 0: int fade time (ms) - time to fade between full volume and silence + UINT32 fade = argV[0]; + + S_FadeOutStopMusic(fade); + + thread->dataStk.push(0); + + return false; +} + +/*-------------------------------------------------- + bool CallFunc_Get/SetLineProperty(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC) + + Generic line property management. +--------------------------------------------------*/ +enum +{ + LINE_PROP_FLAGS, + LINE_PROP_ALPHA, + LINE_PROP_BLENDMODE, + LINE_PROP_ACTIVATION, + LINE_PROP_ACTION, + LINE_PROP_ARG0, + LINE_PROP_ARG1, + LINE_PROP_ARG2, + LINE_PROP_ARG3, + LINE_PROP_ARG4, + LINE_PROP_ARG5, + LINE_PROP_ARG6, + LINE_PROP_ARG7, + LINE_PROP_ARG8, + LINE_PROP_ARG9, + LINE_PROP_ARG0STR, + LINE_PROP_ARG1STR, + LINE_PROP__MAX +}; + +static INT32 NextLine(mtag_t tag, size_t *iterate, INT32 activatorID) +{ + size_t i = *iterate; + *iterate = *iterate + 1; + + if (tag == 0) + { + // 0 grabs the activator. + + if (i != 0) + { + // Don't do more than once. + return -1; + } + + return activatorID; + } + + return Tag_Iterate_Lines(tag, i); +} + +bool CallFunc_GetLineProperty(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC) +{ + (void)thread; + (void)argC; + + auto info = &static_cast<Thread *>(thread)->info; + Environment *env = &ACSEnv; + + mtag_t tag = 0; + size_t tagIt = 0; + + INT32 lineID = 0; + INT32 activatorID = -1; + line_t *line = NULL; + + INT32 property = LINE_PROP__MAX; + INT32 value = 0; + + tag = argV[0]; + + if (info != NULL && info->line != NULL) + { + activatorID = info->line - lines; + } + + if ((lineID = NextLine(tag, &tagIt, activatorID)) != -1) + { + line = &lines[ lineID ]; + } + + property = argV[1]; + + if (line != NULL) + { + +#define PROP_INT(x, y) \ + case x: \ + { \ + value = static_cast<INT32>( line->y ); \ + break; \ + } + +#define PROP_STR(x, y) \ + case x: \ + { \ + value = static_cast<INT32>( ~env->getString( line->y )->idx ); \ + break; \ + } + + switch (property) + { + PROP_INT(LINE_PROP_FLAGS, flags) + PROP_INT(LINE_PROP_ALPHA, alpha) + PROP_INT(LINE_PROP_BLENDMODE, blendmode) + PROP_INT(LINE_PROP_ACTIVATION, activation) + PROP_INT(LINE_PROP_ACTION, special) + PROP_INT(LINE_PROP_ARG0, args[0]) + PROP_INT(LINE_PROP_ARG1, args[1]) + PROP_INT(LINE_PROP_ARG2, args[2]) + PROP_INT(LINE_PROP_ARG3, args[3]) + PROP_INT(LINE_PROP_ARG4, args[4]) + PROP_INT(LINE_PROP_ARG5, args[5]) + PROP_INT(LINE_PROP_ARG6, args[6]) + PROP_INT(LINE_PROP_ARG7, args[7]) + PROP_INT(LINE_PROP_ARG8, args[8]) + PROP_INT(LINE_PROP_ARG9, args[9]) + PROP_STR(LINE_PROP_ARG0STR, stringargs[0]) + PROP_STR(LINE_PROP_ARG1STR, stringargs[1]) + default: + { + CONS_Alert(CONS_WARNING, "GetLineProperty type %d out of range (expected 0 - %d).\n", property, LINE_PROP__MAX-1); + break; + } + } + +#undef PROP_STR +#undef PROP_INT + + } + + thread->dataStk.push(value); + return false; +} + +bool CallFunc_SetLineProperty(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC) +{ + (void)thread; + (void)argC; + + auto info = &static_cast<Thread *>(thread)->info; + //Environment *env = &ACSEnv; + + mtag_t tag = 0; + size_t tagIt = 0; + + INT32 lineID = 0; + INT32 activatorID = -1; + line_t *line = NULL; + + INT32 property = LINE_PROP__MAX; + INT32 value = 0; + + tag = argV[0]; + + if (info != NULL && info->line != NULL) + { + activatorID = info->line - lines; + } + + if ((lineID = NextLine(tag, &tagIt, activatorID)) != -1) + { + line = &lines[ lineID ]; + } + + property = argV[1]; + value = argV[2]; + + while (line != NULL) + { + +#define PROP_READONLY(x, y) \ + case x: \ + { \ + CONS_Alert(CONS_WARNING, "SetLineProperty type '%s' cannot be written to.\n", "y"); \ + break; \ + } + +#define PROP_INT(x, y) \ + case x: \ + { \ + line->y = static_cast< decltype(line->y) >(value); \ + break; \ + } + +#define PROP_STR(x, y) \ + case x: \ + { \ + ACSVM::String *str = thread->scopeMap->getString( value ); \ + if (str->len == 0) \ + { \ + Z_Free(line->y); \ + line->y = NULL; \ + } \ + else \ + { \ + line->y = static_cast<char *>(Z_Realloc(line->y, str->len + 1, PU_LEVEL, NULL)); \ + M_Memcpy(line->y, str->str, str->len + 1); \ + line->y[str->len] = '\0'; \ + } \ + break; \ + } + + switch (property) + { + PROP_INT(LINE_PROP_FLAGS, flags) + PROP_INT(LINE_PROP_ALPHA, alpha) + PROP_INT(LINE_PROP_BLENDMODE, blendmode) + PROP_INT(LINE_PROP_ACTIVATION, activation) + PROP_INT(LINE_PROP_ACTION, special) + PROP_INT(LINE_PROP_ARG0, args[0]) + PROP_INT(LINE_PROP_ARG1, args[1]) + PROP_INT(LINE_PROP_ARG2, args[2]) + PROP_INT(LINE_PROP_ARG3, args[3]) + PROP_INT(LINE_PROP_ARG4, args[4]) + PROP_INT(LINE_PROP_ARG5, args[5]) + PROP_INT(LINE_PROP_ARG6, args[6]) + PROP_INT(LINE_PROP_ARG7, args[7]) + PROP_INT(LINE_PROP_ARG8, args[8]) + PROP_INT(LINE_PROP_ARG9, args[9]) + PROP_STR(LINE_PROP_ARG0STR, stringargs[0]) + PROP_STR(LINE_PROP_ARG1STR, stringargs[1]) + default: + { + CONS_Alert(CONS_WARNING, "SetLineProperty type %d out of range (expected 0 - %d).\n", property, LINE_PROP__MAX-1); + break; + } + } + + if ((lineID = NextLine(tag, &tagIt, activatorID)) != -1) + { + line = &lines[ lineID ]; + } + else + { + line = NULL; + } + +#undef PROP_STR +#undef PROP_INT +#undef PROP_READONLY + + } + + thread->dataStk.push(0); + + return false; +} + +/*-------------------------------------------------- + bool CallFunc_Get/SetSideProperty(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC) + + Generic side property management. +--------------------------------------------------*/ +enum +{ + SIDE_FRONT = 0, + SIDE_BACK = 1, + SIDE_BOTH, +}; + +enum +{ + SIDE_PROP_XOFFSET, + SIDE_PROP_YOFFSET, + SIDE_PROP_TOPTEXTURE, + SIDE_PROP_BOTTOMTEXTURE, + SIDE_PROP_MIDTEXTURE, + SIDE_PROP_REPEATCOUNT, + SIDE_PROP__MAX +}; + +bool CallFunc_GetSideProperty(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC) +{ + (void)thread; + (void)argC; + + auto info = &static_cast<Thread *>(thread)->info; + Environment *env = &ACSEnv; + + mtag_t tag = 0; + size_t tagIt = 0; + + INT32 lineID = 0; + INT32 activatorID = -1; + line_t *line = NULL; + + UINT8 sideID = 0; + side_t *side = NULL; + + INT32 property = SIDE_PROP__MAX; + INT32 value = 0; + + tag = argV[0]; + + if (info != NULL && info->line != NULL) + { + activatorID = info->line - lines; + } + + if ((lineID = NextLine(tag, &tagIt, activatorID)) != -1) + { + line = &lines[ lineID ]; + } + + sideID = argV[1]; + switch (sideID) + { + default: // Activator + case SIDE_BOTH: // Wouldn't make sense for this function. + { + sideID = info->side; + break; + } + case SIDE_FRONT: + case SIDE_BACK: + { + // Keep sideID as is. + break; + } + } + + if (line != NULL && line->sidenum[sideID] != 0xffff) + { + side = &sides[line->sidenum[sideID]]; + } + + property = argV[2]; + + if (side != NULL) + { + +#define PROP_INT(x, y) \ + case x: \ + { \ + value = static_cast<INT32>( side->y ); \ + break; \ + } + +#define PROP_STR(x, y) \ + case x: \ + { \ + value = static_cast<INT32>( ~env->getString( side->y )->idx ); \ + break; \ + } + +#define PROP_TEXTURE(x, y) \ + case x: \ + { \ + value = static_cast<INT32>( ~env->getString( textures[ side->y ]->name )->idx ); \ + break; \ + } + + switch (property) + { + PROP_INT(SIDE_PROP_XOFFSET, textureoffset) + PROP_INT(SIDE_PROP_YOFFSET, rowoffset) + PROP_TEXTURE(SIDE_PROP_TOPTEXTURE, toptexture) + PROP_TEXTURE(SIDE_PROP_BOTTOMTEXTURE, bottomtexture) + PROP_TEXTURE(SIDE_PROP_MIDTEXTURE, midtexture) + PROP_INT(SIDE_PROP_REPEATCOUNT, repeatcnt) + default: + { + CONS_Alert(CONS_WARNING, "GetSideProperty type %d out of range (expected 0 - %d).\n", property, SIDE_PROP__MAX-1); + break; + } + } + +#undef PROP_TEXTURE +#undef PROP_STR +#undef PROP_INT + + } + + thread->dataStk.push(value); + return false; +} + +bool CallFunc_SetSideProperty(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC) +{ + (void)thread; + (void)argC; + + auto info = &static_cast<Thread *>(thread)->info; + //Environment *env = &ACSEnv; + + mtag_t tag = 0; + size_t tagIt = 0; + + INT32 lineID = 0; + INT32 activatorID = -1; + line_t *line = NULL; + + UINT8 sideID = 0; + side_t *side = NULL; + boolean tryBoth = false; + + INT32 property = SIDE_PROP__MAX; + INT32 value = 0; + + tag = argV[0]; + + if (info != NULL && info->line != NULL) + { + activatorID = info->line - lines; + } + + if ((lineID = NextLine(tag, &tagIt, activatorID)) != -1) + { + line = &lines[ lineID ]; + } + + sideID = argV[1]; + switch (sideID) + { + default: // Activator + { + sideID = info->side; + break; + } + case SIDE_BOTH: + { + sideID = SIDE_FRONT; + tryBoth = true; + break; + } + case SIDE_FRONT: + case SIDE_BACK: + { + // Keep sideID as is. + break; + } + } + + if (line != NULL && line->sidenum[sideID] != 0xffff) + { + side = &sides[line->sidenum[sideID]]; + } + + property = argV[2]; + value = argV[3]; + + while (line != NULL) + { + if (side != NULL) + { + +#define PROP_READONLY(x, y) \ + case x: \ + { \ + CONS_Alert(CONS_WARNING, "SetSideProperty type '%s' cannot be written to.\n", "y"); \ + break; \ + } + +#define PROP_INT(x, y) \ + case x: \ + { \ + side->y = static_cast< decltype(side->y) >(value); \ + break; \ + } + +#define PROP_STR(x, y) \ + case x: \ + { \ + ACSVM::String *str = thread->scopeMap->getString( value ); \ + if (str->len == 0) \ + { \ + Z_Free(side->y); \ + side->y = NULL; \ + } \ + else \ + { \ + side->y = static_cast<char *>(Z_Realloc(side->y, str->len + 1, PU_LEVEL, NULL)); \ + M_Memcpy(side->y, str->str, str->len + 1); \ + side->y[str->len] = '\0'; \ + } \ + break; \ + } + +#define PROP_TEXTURE(x, y) \ + case x: \ + { \ + side->y = R_TextureNumForName( thread->scopeMap->getString( value )->str ); \ + break; \ + } + + auto install_interpolator = [side] + { + if (side->acs_interpolated) + return; + side->acs_interpolated = true; + R_CreateInterpolator_SideScroll(nullptr, side); + }; + + switch (property) + { + case SIDE_PROP_XOFFSET: + { + side->textureoffset = static_cast< decltype(side->textureoffset) >(value); + install_interpolator(); + break; + } + case SIDE_PROP_YOFFSET: + { + side->rowoffset = static_cast< decltype(side->rowoffset) >(value); + install_interpolator(); + break; + } + PROP_TEXTURE(SIDE_PROP_TOPTEXTURE, toptexture) + PROP_TEXTURE(SIDE_PROP_BOTTOMTEXTURE, bottomtexture) + PROP_TEXTURE(SIDE_PROP_MIDTEXTURE, midtexture) + PROP_INT(SIDE_PROP_REPEATCOUNT, repeatcnt) + default: + { + CONS_Alert(CONS_WARNING, "SetSideProperty type %d out of range (expected 0 - %d).\n", property, SIDE_PROP__MAX-1); + break; + } + } + } + + if (tryBoth == true && sideID == SIDE_FRONT) + { + sideID = SIDE_BACK; + + if (line->sidenum[sideID] != 0xffff) + { + side = &sides[line->sidenum[sideID]]; + continue; + } + } + + if ((lineID = NextLine(tag, &tagIt, activatorID)) != -1) + { + line = &lines[ lineID ]; + + if (tryBoth == true) + { + sideID = SIDE_FRONT; + } + } + else + { + line = NULL; + } + + if (line != NULL && line->sidenum[sideID] != 0xffff) + { + side = &sides[line->sidenum[sideID]]; + } + else + { + side = NULL; + } + +#undef PROP_TEXTURE +#undef PROP_STR +#undef PROP_INT +#undef PROP_READONLY + + } + + thread->dataStk.push(0); + + return false; +} + +/*-------------------------------------------------- + bool CallFunc_Get/SetSectorProperty(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC) + + Generic sector property management. +--------------------------------------------------*/ +enum +{ + SECTOR_PROP_FLOORHEIGHT, + SECTOR_PROP_CEILINGHEIGHT, + SECTOR_PROP_FLOORPIC, + SECTOR_PROP_CEILINGPIC, + SECTOR_PROP_LIGHTLEVEL, + SECTOR_PROP_FLOORLIGHTLEVEL, + SECTOR_PROP_CEILINGLIGHTLEVEL, + SECTOR_PROP_FLOORLIGHTABSOLUTE, + SECTOR_PROP_CEILINGLIGHTABSOLUTE, + SECTOR_PROP_FLAGS, + SECTOR_PROP_SPECIALFLAGS, + SECTOR_PROP_GRAVITY, + SECTOR_PROP_ACTIVATION, + SECTOR_PROP_ACTION, + SECTOR_PROP_ARG0, + SECTOR_PROP_ARG1, + SECTOR_PROP_ARG2, + SECTOR_PROP_ARG3, + SECTOR_PROP_ARG4, + SECTOR_PROP_ARG5, + SECTOR_PROP_ARG6, + SECTOR_PROP_ARG7, + SECTOR_PROP_ARG8, + SECTOR_PROP_ARG9, + SECTOR_PROP_ARG0STR, + SECTOR_PROP_ARG1STR, + SECTOR_PROP__MAX +}; + +static INT32 NextSector(mtag_t tag, size_t *iterate, INT32 activatorID) +{ + size_t i = *iterate; + *iterate = *iterate + 1; + + if (tag == 0) + { + // 0 grabs the activator. + + if (i != 0) + { + // Don't do more than once. + return -1; + } + + return activatorID; + } + + return Tag_Iterate_Sectors(tag, i); +} + +bool CallFunc_GetSectorProperty(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC) +{ + (void)thread; + (void)argC; + + auto info = &static_cast<Thread *>(thread)->info; + Environment *env = &ACSEnv; + + mtag_t tag = 0; + size_t tagIt = 0; + + INT32 sectorID = 0; + INT32 activatorID = -1; + sector_t *sector = NULL; + + INT32 property = SECTOR_PROP__MAX; + INT32 value = 0; + + tag = argV[0]; + + if (info != NULL && info->sector != NULL) + { + activatorID = info->sector - sectors; + } + + if ((sectorID = NextSector(tag, &tagIt, activatorID)) != -1) + { + sector = §ors[ sectorID ]; + } + + property = argV[1]; + + if (sector != NULL) + { + +#define PROP_INT(x, y) \ + case x: \ + { \ + value = static_cast<INT32>( sector->y ); \ + break; \ + } + +#define PROP_STR(x, y) \ + case x: \ + { \ + value = static_cast<INT32>( ~env->getString( sector->y )->idx ); \ + break; \ + } + +#define PROP_FLAT(x, y) \ + case x: \ + { \ + value = static_cast<INT32>( ~env->getString( levelflats[ sector->y ].name )->idx ); \ + break; \ + } + + switch (property) + { + PROP_INT(SECTOR_PROP_FLOORHEIGHT, floorheight) + PROP_INT(SECTOR_PROP_CEILINGHEIGHT, ceilingheight) + PROP_FLAT(SECTOR_PROP_FLOORPIC, floorpic) + PROP_FLAT(SECTOR_PROP_CEILINGPIC, ceilingpic) + PROP_INT(SECTOR_PROP_LIGHTLEVEL, lightlevel) + PROP_INT(SECTOR_PROP_FLOORLIGHTLEVEL, floorlightlevel) + PROP_INT(SECTOR_PROP_CEILINGLIGHTLEVEL, ceilinglightlevel) + PROP_INT(SECTOR_PROP_FLOORLIGHTABSOLUTE, floorlightabsolute) + PROP_INT(SECTOR_PROP_CEILINGLIGHTABSOLUTE, ceilinglightabsolute) + PROP_INT(SECTOR_PROP_FLAGS, flags) + PROP_INT(SECTOR_PROP_SPECIALFLAGS, specialflags) + PROP_INT(SECTOR_PROP_GRAVITY, gravity) + PROP_INT(SECTOR_PROP_ACTIVATION, activation) + PROP_INT(SECTOR_PROP_ACTION, action) + PROP_INT(SECTOR_PROP_ARG0, args[0]) + PROP_INT(SECTOR_PROP_ARG1, args[1]) + PROP_INT(SECTOR_PROP_ARG2, args[2]) + PROP_INT(SECTOR_PROP_ARG3, args[3]) + PROP_INT(SECTOR_PROP_ARG4, args[4]) + PROP_INT(SECTOR_PROP_ARG5, args[5]) + PROP_INT(SECTOR_PROP_ARG6, args[6]) + PROP_INT(SECTOR_PROP_ARG7, args[7]) + PROP_INT(SECTOR_PROP_ARG8, args[8]) + PROP_INT(SECTOR_PROP_ARG9, args[9]) + PROP_STR(SECTOR_PROP_ARG0STR, stringargs[0]) + PROP_STR(SECTOR_PROP_ARG1STR, stringargs[1]) + default: + { + CONS_Alert(CONS_WARNING, "GetSectorProperty type %d out of range (expected 0 - %d).\n", property, SECTOR_PROP__MAX-1); + break; + } + } + +#undef PROP_FLAT +#undef PROP_STR +#undef PROP_INT + + } + + thread->dataStk.push(value); + return false; +} + +bool CallFunc_SetSectorProperty(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC) +{ + (void)thread; + (void)argC; + + auto info = &static_cast<Thread *>(thread)->info; + //Environment *env = &ACSEnv; + + mtag_t tag = 0; + size_t tagIt = 0; + + INT32 sectorID = 0; + INT32 activatorID = -1; + sector_t *sector = NULL; + + INT32 property = SECTOR_PROP__MAX; + INT32 value = 0; + + tag = argV[0]; + + if (info != NULL && info->sector != NULL) + { + activatorID = info->sector - sectors; + } + + if ((sectorID = NextSector(tag, &tagIt, activatorID)) != -1) + { + sector = §ors[ sectorID ]; + } + + property = argV[1]; + value = argV[2]; + + while (sector != NULL) + { + +#define PROP_READONLY(x, y) \ + case x: \ + { \ + CONS_Alert(CONS_WARNING, "SetSectorProperty type '%s' cannot be written to.\n", "y"); \ + break; \ + } + +#define PROP_INT(x, y) \ + case x: \ + { \ + sector->y = static_cast< decltype(sector->y) >(value); \ + break; \ + } + +#define PROP_STR(x, y) \ + case x: \ + { \ + ACSVM::String *str = thread->scopeMap->getString( value ); \ + if (str->len == 0) \ + { \ + Z_Free(sector->y); \ + sector->y = NULL; \ + } \ + else \ + { \ + sector->y = static_cast<char *>(Z_Realloc(sector->y, str->len + 1, PU_LEVEL, NULL)); \ + M_Memcpy(sector->y, str->str, str->len + 1); \ + sector->y[str->len] = '\0'; \ + } \ + break; \ + } + +#define PROP_FLAT(x, y) \ + case x: \ + { \ + sector->y = P_AddLevelFlatRuntime( thread->scopeMap->getString( value )->str ); \ + break; \ + } + + switch (property) + { + PROP_INT(SECTOR_PROP_FLOORHEIGHT, floorheight) + PROP_INT(SECTOR_PROP_CEILINGHEIGHT, ceilingheight) + PROP_FLAT(SECTOR_PROP_FLOORPIC, floorpic) + PROP_FLAT(SECTOR_PROP_CEILINGPIC, ceilingpic) + PROP_INT(SECTOR_PROP_LIGHTLEVEL, lightlevel) + PROP_INT(SECTOR_PROP_FLOORLIGHTLEVEL, floorlightlevel) + PROP_INT(SECTOR_PROP_CEILINGLIGHTLEVEL, ceilinglightlevel) + PROP_INT(SECTOR_PROP_FLOORLIGHTABSOLUTE, floorlightabsolute) + PROP_INT(SECTOR_PROP_CEILINGLIGHTABSOLUTE, ceilinglightabsolute) + PROP_INT(SECTOR_PROP_FLAGS, flags) + PROP_INT(SECTOR_PROP_SPECIALFLAGS, specialflags) + PROP_INT(SECTOR_PROP_GRAVITY, gravity) + PROP_INT(SECTOR_PROP_ACTIVATION, activation) + PROP_INT(SECTOR_PROP_ACTION, action) + PROP_INT(SECTOR_PROP_ARG0, args[0]) + PROP_INT(SECTOR_PROP_ARG1, args[1]) + PROP_INT(SECTOR_PROP_ARG2, args[2]) + PROP_INT(SECTOR_PROP_ARG3, args[3]) + PROP_INT(SECTOR_PROP_ARG4, args[4]) + PROP_INT(SECTOR_PROP_ARG5, args[5]) + PROP_INT(SECTOR_PROP_ARG6, args[6]) + PROP_INT(SECTOR_PROP_ARG7, args[7]) + PROP_INT(SECTOR_PROP_ARG8, args[8]) + PROP_INT(SECTOR_PROP_ARG9, args[9]) + PROP_STR(SECTOR_PROP_ARG0STR, stringargs[0]) + PROP_STR(SECTOR_PROP_ARG1STR, stringargs[1]) + default: + { + CONS_Alert(CONS_WARNING, "SetSectorProperty type %d out of range (expected 0 - %d).\n", property, SECTOR_PROP__MAX-1); + break; + } + } + + if ((sectorID = NextSector(tag, &tagIt, activatorID)) != -1) + { + sector = §ors[ sectorID ]; + } + else + { + sector = NULL; + } + +#undef PROP_FLAT +#undef PROP_STR +#undef PROP_INT +#undef PROP_READONLY + + } + + thread->dataStk.push(0); + + return false; +} + +/*-------------------------------------------------- + bool CallFunc_Get/SetThingProperty(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC) + + Generic thing property management. +--------------------------------------------------*/ +enum +{ + THING_PROP_X, + THING_PROP_Y, + THING_PROP_Z, + THING_PROP_TYPE, + THING_PROP_ANGLE, + THING_PROP_PITCH, + THING_PROP_ROLL, + THING_PROP_SPRITEROLL, + THING_PROP_FRAME, + THING_PROP_SPRITE, + THING_PROP_SPRITE2, + THING_PROP_RENDERFLAGS, + THING_PROP_SPRITEXSCALE, + THING_PROP_SPRITEYSCALE, + THING_PROP_SPRITEXOFFSET, + THING_PROP_SPRITEYOFFSET, + THING_PROP_FLOORZ, + THING_PROP_CEILINGZ, + THING_PROP_RADIUS, + THING_PROP_HEIGHT, + THING_PROP_MOMX, + THING_PROP_MOMY, + THING_PROP_MOMZ, + THING_PROP_TICS, + THING_PROP_STATE, + THING_PROP_FLAGS, + THING_PROP_FLAGS2, + THING_PROP_EFLAGS, + THING_PROP_SKIN, + THING_PROP_COLOR, + THING_PROP_HEALTH, + THING_PROP_MOVEDIR, + THING_PROP_MOVECOUNT, + THING_PROP_REACTIONTIME, + THING_PROP_THRESHOLD, + THING_PROP_LASTLOOK, + THING_PROP_FRICTION, + THING_PROP_MOVEFACTOR, + THING_PROP_FUSE, + THING_PROP_WATERTOP, + THING_PROP_WATERBOTTOM, + THING_PROP_SCALE, + THING_PROP_DESTSCALE, + THING_PROP_SCALESPEED, + THING_PROP_EXTRAVALUE1, + THING_PROP_EXTRAVALUE2, + THING_PROP_CUSVAL, + THING_PROP_CVMEM, + THING_PROP_COLORIZED, + THING_PROP_MIRRORED, + THING_PROP_SHADOWSCALE, + THING_PROP_DISPOFFSET, + THING_PROP_TARGET, + THING_PROP_TRACER, + THING_PROP_HNEXT, + THING_PROP_HPREV, + THING_PROP__MAX +}; + +bool CallFunc_GetThingProperty(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC) +{ + (void)thread; + (void)argC; + + auto info = &static_cast<Thread *>(thread)->info; + Environment *env = &ACSEnv; + + mtag_t tag = 0; + mobj_t *mobj = NULL; + + INT32 property = SECTOR_PROP__MAX; + INT32 value = 0; + + tag = argV[0]; + mobj = P_FindMobjFromTID(tag, mobj, info->mo); + + property = argV[1]; + + if (mobj != NULL) + { + +#define PROP_INT(x, y) \ + case x: \ + { \ + value = static_cast<INT32>( mobj->y ); \ + break; \ + } + +#define PROP_STR(x, y) \ + case x: \ + { \ + value = static_cast<INT32>( ~env->getString( mobj->y )->idx ); \ + break; \ + } + +#define PROP_ANGLE(x, y) \ + case x: \ + { \ + value = static_cast<INT32>( AngleFixed( mobj->y ) ); \ + break; \ + } + +#define PROP_TYPE(x, y) \ + case x: \ + { \ + if (mobj->y >= MT_FIRSTFREESLOT) \ + { \ + std::string prefix = "MT_"; \ + std::string full = prefix + FREE_MOBJS[mobj->y - MT_FIRSTFREESLOT]; \ + value = static_cast<INT32>( ~env->getString( full.c_str() )->idx ); \ + } \ + else \ + { \ + value = static_cast<INT32>( ~env->getString( MOBJTYPE_LIST[ mobj->y ] )->idx ); \ + } \ + break; \ + } + +#define PROP_SPR(x, y) \ + case x: \ + { \ + char crunched[5] = {0}; \ + strncpy(crunched, sprnames[ mobj->y ], 4); \ + value = static_cast<INT32>( ~env->getString( crunched )->idx ); \ + break; \ + } + +#define PROP_SPR2(x, y) \ + case x: \ + { \ + value = static_cast<INT32>( ~env->getString( spr2names[ mobj->y ] )->idx ); \ + break; \ + } + +#define PROP_STATE(x, y) \ + case x: \ + { \ + statenum_t stateID = static_cast<statenum_t>(mobj->y - states); \ + if (stateID >= S_FIRSTFREESLOT) \ + { \ + std::string prefix = "S_"; \ + std::string full = prefix + FREE_STATES[stateID - S_FIRSTFREESLOT]; \ + value = static_cast<INT32>( ~env->getString( full.c_str() )->idx ); \ + } \ + else \ + { \ + value = static_cast<INT32>( ~env->getString( STATE_LIST[ stateID ] )->idx ); \ + } \ + break; \ + } + +#define PROP_SKIN(x, y) \ + case x: \ + { \ + if (mobj->y != NULL) \ + { \ + skin_t *skin = static_cast<skin_t *>(mobj->y); \ + value = static_cast<INT32>( ~env->getString( skin->name )->idx ); \ + } \ + break; \ + } + +#define PROP_COLOR(x, y) \ + case x: \ + { \ + value = static_cast<INT32>( ~env->getString( skincolors[ mobj->y ].name )->idx ); \ + break; \ + } + +#define PROP_MOBJ(x, y) \ + case x: \ + { \ + if (P_MobjWasRemoved(mobj->y) == false) \ + { \ + value = static_cast<INT32>( mobj->y->tid ); \ + } \ + break; \ + } + + switch (property) + { + PROP_INT(THING_PROP_X, x) + PROP_INT(THING_PROP_Y, y) + PROP_INT(THING_PROP_Z, z) + PROP_TYPE(THING_PROP_TYPE, type) + PROP_ANGLE(THING_PROP_ANGLE, angle) + PROP_ANGLE(THING_PROP_PITCH, pitch) + PROP_ANGLE(THING_PROP_ROLL, roll) + PROP_ANGLE(THING_PROP_SPRITEROLL, spriteroll) + PROP_INT(THING_PROP_FRAME, frame) + PROP_SPR(THING_PROP_SPRITE, sprite) + PROP_SPR2(THING_PROP_SPRITE2, sprite2) + PROP_INT(THING_PROP_RENDERFLAGS, renderflags) + PROP_INT(THING_PROP_SPRITEXSCALE, spritexscale) + PROP_INT(THING_PROP_SPRITEYSCALE, spriteyscale) + PROP_INT(THING_PROP_SPRITEXOFFSET, spritexoffset) + PROP_INT(THING_PROP_SPRITEYOFFSET, spriteyoffset) + PROP_INT(THING_PROP_FLOORZ, floorz) + PROP_INT(THING_PROP_CEILINGZ, ceilingz) + PROP_INT(THING_PROP_RADIUS, radius) + PROP_INT(THING_PROP_HEIGHT, height) + PROP_INT(THING_PROP_MOMX, momx) + PROP_INT(THING_PROP_MOMY, momy) + PROP_INT(THING_PROP_MOMZ, momz) + PROP_INT(THING_PROP_TICS, tics) + PROP_STATE(THING_PROP_STATE, state) + PROP_INT(THING_PROP_FLAGS, flags) + PROP_INT(THING_PROP_FLAGS2, flags2) + PROP_INT(THING_PROP_EFLAGS, eflags) + PROP_SKIN(THING_PROP_SKIN, skin) + PROP_COLOR(THING_PROP_COLOR, color) + PROP_INT(THING_PROP_HEALTH, health) + PROP_INT(THING_PROP_MOVEDIR, movedir) + PROP_INT(THING_PROP_MOVECOUNT, movecount) + PROP_INT(THING_PROP_REACTIONTIME, reactiontime) + PROP_INT(THING_PROP_THRESHOLD, threshold) + PROP_INT(THING_PROP_LASTLOOK, lastlook) + PROP_INT(THING_PROP_FRICTION, friction) + PROP_INT(THING_PROP_MOVEFACTOR, movefactor) + PROP_INT(THING_PROP_FUSE, fuse) + PROP_INT(THING_PROP_WATERTOP, watertop) + PROP_INT(THING_PROP_WATERBOTTOM, waterbottom) + PROP_INT(THING_PROP_SCALE, scale) + PROP_INT(THING_PROP_DESTSCALE, destscale) + PROP_INT(THING_PROP_SCALESPEED, scalespeed) + PROP_INT(THING_PROP_EXTRAVALUE1, extravalue1) + PROP_INT(THING_PROP_EXTRAVALUE2, extravalue2) + PROP_INT(THING_PROP_CUSVAL, cusval) + PROP_INT(THING_PROP_CVMEM, cvmem) + PROP_INT(THING_PROP_COLORIZED, colorized) + PROP_INT(THING_PROP_MIRRORED, mirrored) + PROP_INT(THING_PROP_SHADOWSCALE, shadowscale) + PROP_INT(THING_PROP_DISPOFFSET, dispoffset) + PROP_MOBJ(THING_PROP_TARGET, target) + PROP_MOBJ(THING_PROP_TRACER, tracer) + PROP_MOBJ(THING_PROP_HNEXT, hnext) + PROP_MOBJ(THING_PROP_HPREV, hprev) + default: + { + CONS_Alert(CONS_WARNING, "GetThingProperty type %d out of range (expected 0 - %d).\n", property, THING_PROP__MAX-1); + break; + } + } + +#undef PROP_MOBJ +#undef PROP_COLOR +#undef PROP_SKIN +#undef PROP_STATE +#undef PROP_SPR2 +#undef PROP_SPR +#undef PROP_TYPE +#undef PROP_ANGLE +#undef PROP_STR +#undef PROP_INT + + } + + thread->dataStk.push(value); + return false; +} + +bool CallFunc_SetThingProperty(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC) +{ + (void)thread; + (void)argC; + + auto info = &static_cast<Thread *>(thread)->info; + //Environment *env = &ACSEnv; + + mtag_t tag = 0; + mobj_t *mobj = NULL; + + INT32 property = SECTOR_PROP__MAX; + INT32 value = 0; + + tag = argV[0]; + mobj = P_FindMobjFromTID(tag, mobj, info->mo); + + property = argV[1]; + value = argV[2]; + + while (mobj != NULL) + { + +#define PROP_READONLY(x, y) \ + case x: \ + { \ + CONS_Alert(CONS_WARNING, "SetThingProperty type '%s' cannot be written to.\n", "y"); \ + break; \ + } + +#define PROP_INT(x, y) \ + case x: \ + { \ + mobj->y = static_cast< decltype(mobj->y) >(value); \ + break; \ + } + +#define PROP_STR(x, y) \ + case x: \ + { \ + ACSVM::String *str = thread->scopeMap->getString( value ); \ + if (str->len == 0) \ + { \ + Z_Free(mobj->y); \ + mobj->y = NULL; \ + } \ + else \ + { \ + mobj->y = static_cast<char *>(Z_Realloc(mobj->y, str->len + 1, PU_LEVEL, NULL)); \ + M_Memcpy(mobj->y, str->str, str->len + 1); \ + mobj->y[str->len] = '\0'; \ + } \ + break; \ + } + +#define PROP_ANGLE(x, y) \ + case x: \ + { \ + mobj->y = static_cast<angle_t>( FixedAngle(value) ); \ + break; \ + } + +#define PROP_TYPE(x, y) \ + case x: \ + { \ + if (mobj->player == NULL) \ + { \ + mobjtype_t newType = mobj->y; \ + bool success = ACS_GetMobjTypeFromString(thread->scopeMap->getString( value )->str, &newType); \ + if (success == true) \ + { \ + mobj->y = newType; \ + mobj->info = &mobjinfo[newType]; \ + P_SetScale(mobj, mobj->scale, false); \ + } \ + } \ + break; \ + } + +#define PROP_SPR(x, y) \ + case x: \ + { \ + spritenum_t newSprite = mobj->y; \ + bool success = ACS_GetSpriteFromString(thread->scopeMap->getString( value )->str, &newSprite); \ + if (success == true) \ + { \ + mobj->y = newSprite; \ + } \ + break; \ + } + +#define PROP_SPR2(x, y) \ + case x: \ + { \ + playersprite_t newSprite2 = static_cast<playersprite_t>(mobj->y); \ + bool success = ACS_GetSprite2FromString(thread->scopeMap->getString( value )->str, &newSprite2); \ + if (success == true) \ + { \ + mobj->y = static_cast< decltype(mobj->y) >(newSprite2); \ + } \ + break; \ + } + +#define PROP_STATE(x, y) \ + case x: \ + { \ + statenum_t newState = static_cast<statenum_t>(mobj->y - states); \ + bool success = ACS_GetStateFromString(thread->scopeMap->getString( value )->str, &newState); \ + if (success == true) \ + { \ + P_SetMobjState(mobj, newState); \ + } \ + break; \ + } + +#define PROP_SKIN(x, y) \ + case x: \ + { \ + INT32 newSkin = (mobj->skin != NULL) ? (static_cast<skin_t *>(mobj->skin))->skinnum : -1; \ + bool success = ACS_GetSkinFromString(thread->scopeMap->getString( value )->str, &newSkin); \ + if (success == true) \ + { \ + mobj->y = (newSkin >= 0 && newSkin < numskins) ? &skins[ newSkin ] : NULL; \ + } \ + break; \ + } + +#define PROP_COLOR(x, y) \ + case x: \ + { \ + skincolornum_t newColor = static_cast<skincolornum_t>(mobj->y); \ + bool success = ACS_GetColorFromString(thread->scopeMap->getString( value )->str, &newColor); \ + if (success == true) \ + { \ + mobj->y = static_cast< decltype(mobj->y) >(newColor); \ + } \ + break; \ + } + +#define PROP_MOBJ(x, y) \ + case x: \ + { \ + mobj_t *newTarget = P_FindMobjFromTID(value, NULL, NULL); \ + P_SetTarget(&mobj->y, newTarget); \ + break; \ + } + +#define PROP_SCALE(x, y) \ + case x: \ + { \ + P_SetScale(mobj, value, false); \ + break; \ + } + +#define PROP_FLAGS(x, y) \ + case x: \ + { \ + if ((value & (MF_NOBLOCKMAP|MF_NOSECTOR)) != (mobj->y & (MF_NOBLOCKMAP|MF_NOSECTOR))) \ + { \ + P_UnsetThingPosition(mobj); \ + mobj->y = value; \ + if ((value & MF_NOSECTOR) && sector_list) \ + { \ + P_DelSeclist(sector_list); \ + sector_list = NULL; \ + } \ + mobj->snext = NULL, mobj->sprev = NULL; \ + P_SetThingPosition(mobj); \ + } \ + else \ + { \ + mobj->y = value; \ + } \ + break; \ + } + + switch (property) + { + PROP_READONLY(THING_PROP_X, x) + PROP_READONLY(THING_PROP_Y, y) + PROP_READONLY(THING_PROP_Z, z) + PROP_TYPE(THING_PROP_TYPE, type) + PROP_ANGLE(THING_PROP_ANGLE, angle) + PROP_ANGLE(THING_PROP_PITCH, pitch) + PROP_ANGLE(THING_PROP_ROLL, roll) + PROP_ANGLE(THING_PROP_SPRITEROLL, spriteroll) + PROP_INT(THING_PROP_FRAME, frame) + PROP_SPR(THING_PROP_SPRITE, sprite) + PROP_SPR2(THING_PROP_SPRITE2, sprite2) + PROP_INT(THING_PROP_RENDERFLAGS, renderflags) + PROP_INT(THING_PROP_SPRITEXSCALE, spritexscale) + PROP_INT(THING_PROP_SPRITEYSCALE, spriteyscale) + PROP_INT(THING_PROP_SPRITEXOFFSET, spritexoffset) + PROP_INT(THING_PROP_SPRITEYOFFSET, spriteyoffset) + PROP_INT(THING_PROP_FLOORZ, floorz) + PROP_INT(THING_PROP_CEILINGZ, ceilingz) + PROP_READONLY(THING_PROP_RADIUS, radius) + PROP_READONLY(THING_PROP_HEIGHT, height) + PROP_INT(THING_PROP_MOMX, momx) + PROP_INT(THING_PROP_MOMY, momy) + PROP_INT(THING_PROP_MOMZ, momz) + PROP_INT(THING_PROP_TICS, tics) + PROP_STATE(THING_PROP_STATE, state) + PROP_FLAGS(THING_PROP_FLAGS, flags) + PROP_INT(THING_PROP_FLAGS2, flags2) + PROP_INT(THING_PROP_EFLAGS, eflags) + PROP_SKIN(THING_PROP_SKIN, skin) + PROP_COLOR(THING_PROP_COLOR, color) + PROP_INT(THING_PROP_HEALTH, health) + PROP_INT(THING_PROP_MOVEDIR, movedir) + PROP_INT(THING_PROP_MOVECOUNT, movecount) + PROP_INT(THING_PROP_REACTIONTIME, reactiontime) + PROP_INT(THING_PROP_THRESHOLD, threshold) + PROP_INT(THING_PROP_LASTLOOK, lastlook) + PROP_INT(THING_PROP_FRICTION, friction) + PROP_INT(THING_PROP_MOVEFACTOR, movefactor) + PROP_INT(THING_PROP_FUSE, fuse) + PROP_INT(THING_PROP_WATERTOP, watertop) + PROP_INT(THING_PROP_WATERBOTTOM, waterbottom) + PROP_SCALE(THING_PROP_SCALE, scale) + PROP_INT(THING_PROP_DESTSCALE, destscale) + PROP_INT(THING_PROP_SCALESPEED, scalespeed) + PROP_INT(THING_PROP_EXTRAVALUE1, extravalue1) + PROP_INT(THING_PROP_EXTRAVALUE2, extravalue2) + PROP_INT(THING_PROP_CUSVAL, cusval) + PROP_INT(THING_PROP_CVMEM, cvmem) + PROP_INT(THING_PROP_COLORIZED, colorized) + PROP_INT(THING_PROP_MIRRORED, mirrored) + PROP_INT(THING_PROP_SHADOWSCALE, shadowscale) + PROP_INT(THING_PROP_DISPOFFSET, dispoffset) + PROP_MOBJ(THING_PROP_TARGET, target) + PROP_MOBJ(THING_PROP_TRACER, tracer) + PROP_MOBJ(THING_PROP_HNEXT, hnext) + PROP_MOBJ(THING_PROP_HPREV, hprev) + default: + { + CONS_Alert(CONS_WARNING, "SetThingProperty type %d out of range (expected 0 - %d).\n", property, THING_PROP__MAX-1); + break; + } + } + + mobj = P_FindMobjFromTID(tag, mobj, info->mo); + +#undef PROP_FLAGS +#undef PROP_SCALE +#undef PROP_MOBJ +#undef PROP_COLOR +#undef PROP_SKIN +#undef PROP_STATE +#undef PROP_SPR2 +#undef PROP_SPR +#undef PROP_TYPE +#undef PROP_ANGLE +#undef PROP_STR +#undef PROP_INT +#undef PROP_READONLY + + } + + thread->dataStk.push(0); + + return false; +} + +//not needed +#if 0 +bool CallFunc_AddMessage(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC) +{ + (void)argC; + + ACSVM::MapScope *map = thread->scopeMap; + + CONS_Printf(map->getString(argV[0])->str); + + thread->dataStk.push(0); + + return false; +} + +bool CallFunc_AddMessageForPlayer(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC) +{ + (void)argC; + + ACSVM::MapScope *map = thread->scopeMap; + + auto info = &static_cast<Thread *>(thread)->info; + + if ((info != NULL) + && (info->mo != NULL && P_MobjWasRemoved(info->mo) == false) + && (info->mo->player != NULL)) + { + if (ACS_ActivatorIsLocal(thread)) + CONS_Printf(map->getString(argV[0])->str); + } + + thread->dataStk.push(0); + + return false; +} +#endif diff --git a/src/acs/call-funcs.hpp b/src/acs/call-funcs.hpp new file mode 100644 index 0000000000000000000000000000000000000000..c0a88a862a37bec173bf48152aeb27482c7c4999 --- /dev/null +++ b/src/acs/call-funcs.hpp @@ -0,0 +1,115 @@ +// SONIC ROBO BLAST 2 +//----------------------------------------------------------------------------- +// Copyright (C) 2024 by Russell's Smart Interfaces +// Copyright (C) 2024 by Sonic Team Junior. +// Copyright (C) 2016 by James Haley, David Hill, et al. (Team Eternity) +// Copyright (C) 2024 by Sally "TehRealSalt" Cochenour +// Copyright (C) 2024 by Kart Krew +// +// This program is free software distributed under the +// terms of the GNU General Public License, version 2. +// See the 'LICENSE' file for more details. +//----------------------------------------------------------------------------- +/// \file call-funcs.hpp +/// \brief Action Code Script: CallFunc instructions + +#ifndef __SRB2_ACS_CALL_FUNCS_HPP__ +#define __SRB2_ACS_CALL_FUNCS_HPP__ + +#include "acsvm.hpp" + +/*-------------------------------------------------- + bool CallFunc_???(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC); + + These are the actual CallFuncs ran when ACS + is executed. Which CallFuncs are executed + is based on the indices from the compiled + data. ACS_EnvConstruct is where the link + between the byte code and the actual function + is made. + + Input Arguments:- + thread: The ACS execution thread this action + is running on. + argV: An array of the action's arguments. + argC: The length of the argument array. + + Return:- + Returns true if this function pauses the + thread's execution. Otherwise returns false. +--------------------------------------------------*/ + +bool CallFunc_Random(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC); +bool CallFunc_ThingCount(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC); +bool CallFunc_TagWait(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC); +bool CallFunc_PolyWait(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC); +bool CallFunc_CameraWait(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC); +bool CallFunc_ChangeFloor(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC); +bool CallFunc_ChangeCeiling(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC); +bool CallFunc_LineSide(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC); +bool CallFunc_ClearLineSpecial(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC); +bool CallFunc_EndPrint(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC); +bool CallFunc_PlayerCount(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC); +bool CallFunc_GameType(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC); +bool CallFunc_Timer(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC); +bool CallFunc_IsNetworkGame(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC); +bool CallFunc_SectorSound(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC); +bool CallFunc_AmbientSound(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC); +bool CallFunc_SetLineTexture(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC); +bool CallFunc_SetLineSpecial(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC); +bool CallFunc_ChangeSky(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC); +bool CallFunc_ThingSound(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC); +bool CallFunc_EndPrintBold(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC); +bool CallFunc_PlayerTeam(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC); +bool CallFunc_PlayerRings(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC); +bool CallFunc_PlayerScore(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC); +bool CallFunc_PlayerNumber(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC); +bool CallFunc_ActivatorTID(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC); +bool CallFunc_EndLog(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC); + +bool CallFunc_strcmp(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC); +bool CallFunc_strcasecmp(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC); + +bool CallFunc_CountEnemies(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC); +bool CallFunc_CountPushables(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC); +bool CallFunc_HaveUnlockable(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC); +bool CallFunc_PlayerSkin(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC); +bool CallFunc_PlayerBot(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC); +bool CallFunc_PlayerExiting(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC); +bool CallFunc_SetObjectDye(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC); +bool CallFunc_GetObjectDye(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC); +bool CallFunc_PlayerEmeralds(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC); +bool CallFunc_PlayerLap(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC); +bool CallFunc_LowestLap(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC); +bool CallFunc_RingSlingerMode(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC); +bool CallFunc_CaptureTheFlagMode(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC); +bool CallFunc_TeamGame(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC); +bool CallFunc_ModeAttacking(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC); +bool CallFunc_RecordAttack(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC); +bool CallFunc_NiGHTSAttack(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC); + +bool CallFunc_SetLineRenderStyle(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC); + +bool CallFunc_MapWarp(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC); +bool CallFunc_AddBot(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC); +bool CallFunc_ExitLevel(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC); +bool CallFunc_MusicPlay(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC); +bool CallFunc_MusicStopAll(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC); +bool CallFunc_MusicRestore(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC); +bool CallFunc_MusicDim(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC); + +bool CallFunc_GetLineProperty(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC); +bool CallFunc_SetLineProperty(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC); +bool CallFunc_GetSideProperty(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC); +bool CallFunc_SetSideProperty(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC); +bool CallFunc_GetSectorProperty(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC); +bool CallFunc_SetSectorProperty(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC); +bool CallFunc_GetThingProperty(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC); +bool CallFunc_SetThingProperty(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC); + +#if 0 +bool CallFunc_AddMessage(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC); +bool CallFunc_AddMessageForPlayer(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC); +#endif + +#endif // __SRB2_ACS_CALL_FUNCS_HPP__ diff --git a/src/acs/environment.cpp b/src/acs/environment.cpp new file mode 100644 index 0000000000000000000000000000000000000000..2bcaa1d88fb088587d1877f67b0308cb27273322 --- /dev/null +++ b/src/acs/environment.cpp @@ -0,0 +1,395 @@ +// SONIC ROBO BLAST 2 +//----------------------------------------------------------------------------- +// Copyright (C) 2024 by Russell's Smart Interfaces +// Copyright (C) 2024 by Sonic Team Junior. +// Copyright (C) 2016 by James Haley, David Hill, et al. (Team Eternity) +// Copyright (C) 2024 by Sally "TehRealSalt" Cochenour +// Copyright (C) 2024 by Kart Krew +// +// This program is free software distributed under the +// terms of the GNU General Public License, version 2. +// See the 'LICENSE' file for more details. +//----------------------------------------------------------------------------- +/// \file environment.cpp +/// \brief Action Code Script: Environment definition + +#include <algorithm> +#include <vector> + +#include "acsvm.hpp" + +#include "../doomtype.h" +#include "../doomdef.h" +#include "../doomstat.h" + +#include "../r_defs.h" +#include "../r_state.h" +#include "../g_game.h" +#include "../p_spec.h" +#include "../w_wad.h" +#include "../z_zone.h" +#include "../p_local.h" +#include "../p_spec.h" + +#include "environment.hpp" +#include "thread.hpp" +#include "call-funcs.hpp" +#include "../cxxutil.hpp" + +using namespace srb2::acs; + +Environment ACSEnv; + +Environment::Environment() +{ + ACSVM::GlobalScope *global = getGlobalScope(0); + + // Activate global scope immediately, since we don't want it off. + // Not that we're adding any modules to it, though. :p + global->active = true; + + // Add the data & function pointers. + + // Starting with raw ACS0 codes. I'm using this classic-style + // format here to have a blueprint for what needs implementing, + // but it'd also be fine to move these to new style. + + // See also: + // - https://doomwiki.org/wiki/ACS0_instruction_set + // - https://github.com/DavidPH/ACSVM/blob/master/ACSVM/CodeData.hpp + // - https://github.com/DavidPH/ACSVM/blob/master/ACSVM/CodeList.hpp + + // 0 to 56: Implemented by ACSVM + addCodeDataACS0( 57, {"", 2, addCallFunc(CallFunc_Random)}); + addCodeDataACS0( 58, {"WW", 0, addCallFunc(CallFunc_Random)}); + addCodeDataACS0( 59, {"", 2, addCallFunc(CallFunc_ThingCount)}); + addCodeDataACS0( 60, {"WW", 0, addCallFunc(CallFunc_ThingCount)}); + addCodeDataACS0( 61, {"", 1, addCallFunc(CallFunc_TagWait)}); + addCodeDataACS0( 62, {"W", 0, addCallFunc(CallFunc_TagWait)}); + addCodeDataACS0( 63, {"", 1, addCallFunc(CallFunc_PolyWait)}); + addCodeDataACS0( 64, {"W", 0, addCallFunc(CallFunc_PolyWait)}); + addCodeDataACS0( 65, {"", 2, addCallFunc(CallFunc_ChangeFloor)}); + addCodeDataACS0( 66, {"WWS", 0, addCallFunc(CallFunc_ChangeFloor)}); + addCodeDataACS0( 67, {"", 2, addCallFunc(CallFunc_ChangeCeiling)}); + addCodeDataACS0( 68, {"WWS", 0, addCallFunc(CallFunc_ChangeCeiling)}); + // 69 to 79: Implemented by ACSVM + addCodeDataACS0( 80, {"", 0, addCallFunc(CallFunc_LineSide)}); + // 81 to 82: Implemented by ACSVM + addCodeDataACS0( 83, {"", 0, addCallFunc(CallFunc_ClearLineSpecial)}); + // 84 to 85: Implemented by ACSVM + addCodeDataACS0( 86, {"", 0, addCallFunc(CallFunc_EndPrint)}); + // 87 to 89: Implemented by ACSVM + addCodeDataACS0( 90, {"", 0, addCallFunc(CallFunc_PlayerCount)}); + addCodeDataACS0( 91, {"", 0, addCallFunc(CallFunc_GameType)}); + // addCodeDataACS0( 92, {"", 0, addCallFunc(CallFunc_GameSkill)}); + addCodeDataACS0( 93, {"", 0, addCallFunc(CallFunc_Timer)}); + addCodeDataACS0( 94, {"", 2, addCallFunc(CallFunc_SectorSound)}); + addCodeDataACS0( 95, {"", 2, addCallFunc(CallFunc_AmbientSound)}); + + addCodeDataACS0( 97, {"", 4, addCallFunc(CallFunc_SetLineTexture)}); + + addCodeDataACS0( 99, {"", 7, addCallFunc(CallFunc_SetLineSpecial)}); + addCodeDataACS0(100, {"", 3, addCallFunc(CallFunc_ThingSound)}); + addCodeDataACS0(101, {"", 0, addCallFunc(CallFunc_EndPrintBold)}); + // Hexen p-codes end here + + // Skulltag p-codes begin here + addCodeDataACS0(118, {"", 0, addCallFunc(CallFunc_IsNetworkGame)}); + addCodeDataACS0(119, {"", 0, addCallFunc(CallFunc_PlayerTeam)}); + addCodeDataACS0(120, {"", 0, addCallFunc(CallFunc_PlayerRings)}); + + addCodeDataACS0(122, {"", 0, addCallFunc(CallFunc_PlayerScore)}); + + // 136 to 137: Implemented by ACSVM + + // 157: Implemented by ACSVM + + // 167 to 173: Implemented by ACSVM + addCodeDataACS0(174, {"BB", 0, addCallFunc(CallFunc_Random)}); + // 175 to 179: Implemented by ACSVM + + // 181 to 189: Implemented by ACSVM + + // 203 to 217: Implemented by ACSVM + + // 225 to 243: Implemented by ACSVM + + addCodeDataACS0(247, {"", 0, addCallFunc(CallFunc_PlayerNumber)}); + addCodeDataACS0(248, {"", 0, addCallFunc(CallFunc_ActivatorTID)}); + + // 253: Implemented by ACSVM + + // 256 to 257: Implemented by ACSVM + + // 263: Implemented by ACSVM + addCodeDataACS0(266, {"", 2, addCallFunc(CallFunc_ChangeSky)}); // reimplements linedef type 423 + addCodeDataACS0(270, {"", 0, addCallFunc(CallFunc_EndLog)}); + // 273 to 275: Implemented by ACSVM + + // 291 to 325: Implemented by ACSVM + + // 330: Implemented by ACSVM + + // 349 to 361: Implemented by ACSVM + + // 363 to 381: Implemented by ACSVM + + // Now for new style functions. + // This style is preferred for added functions + // that aren't mimicing one from Hexen's or ZDoom's + // ACS implementations. + addFuncDataACS0( 1, addCallFunc(CallFunc_GetLineProperty)); + addFuncDataACS0( 2, addCallFunc(CallFunc_SetLineProperty)); + // addFuncDataACS0( 3, addCallFunc(CallFunc_GetLineUserProperty)); + addFuncDataACS0( 4, addCallFunc(CallFunc_GetSectorProperty)); + addFuncDataACS0( 5, addCallFunc(CallFunc_SetSectorProperty)); + // addFuncDataACS0( 6, addCallFunc(CallFunc_GetSectorUserProperty)); + addFuncDataACS0( 7, addCallFunc(CallFunc_GetSideProperty)); + addFuncDataACS0( 8, addCallFunc(CallFunc_SetSideProperty)); + // addFuncDataACS0( 9, addCallFunc(CallFunc_GetSideUserProperty)); + addFuncDataACS0( 10, addCallFunc(CallFunc_GetThingProperty)); + addFuncDataACS0( 11, addCallFunc(CallFunc_SetThingProperty)); + // addFuncDataACS0( 12, addCallFunc(CallFunc_GetThingUserProperty)); + // addFuncDataACS0( 13, addCallFunc(CallFunc_GetPlayerProperty)); + // addFuncDataACS0( 14, addCallFunc(CallFunc_SetPlayerProperty)); + // addFuncDataACS0( 15, addCallFunc(CallFunc_GetPolyobjProperty)); + // addFuncDataACS0( 16, addCallFunc(CallFunc_SetPolyobjProperty)); + + addFuncDataACS0( 100, addCallFunc(CallFunc_strcmp)); + addFuncDataACS0( 101, addCallFunc(CallFunc_strcasecmp)); + + addFuncDataACS0( 300, addCallFunc(CallFunc_CountEnemies)); + addFuncDataACS0( 301, addCallFunc(CallFunc_CountPushables)); + // addFuncDataACS0( 302, addCallFunc(CallFunc_Dummy1)); + addFuncDataACS0( 303, addCallFunc(CallFunc_HaveUnlockable)); + addFuncDataACS0( 304, addCallFunc(CallFunc_PlayerSkin)); + addFuncDataACS0( 305, addCallFunc(CallFunc_GetObjectDye)); + addFuncDataACS0( 306, addCallFunc(CallFunc_PlayerEmeralds)); + addFuncDataACS0( 307, addCallFunc(CallFunc_PlayerLap)); + addFuncDataACS0( 308, addCallFunc(CallFunc_LowestLap)); + addFuncDataACS0( 309, addCallFunc(CallFunc_RingSlingerMode)); + addFuncDataACS0( 310, addCallFunc(CallFunc_TeamGame)); + addFuncDataACS0( 311, addCallFunc(CallFunc_RecordAttack)); + addFuncDataACS0( 312, addCallFunc(CallFunc_SetObjectDye)); + addFuncDataACS0( 313, addCallFunc(CallFunc_CaptureTheFlagMode)); + addFuncDataACS0( 315, addCallFunc(CallFunc_PlayerBot)); + addFuncDataACS0( 316, addCallFunc(CallFunc_ModeAttacking)); + addFuncDataACS0( 317, addCallFunc(CallFunc_NiGHTSAttack)); + // addFuncDataACS0( 318, addCallFunc(CallFunc_Dummy3)); + // addFuncDataACS0( 319, addCallFunc(CallFunc_Dummy4)); + addFuncDataACS0( 320, addCallFunc(CallFunc_PlayerExiting)); + + addFuncDataACS0( 500, addCallFunc(CallFunc_CameraWait)); + // addFuncDataACS0( 501, addCallFunc(CallFunc_Dummy5)); + // addFuncDataACS0( 502, addCallFunc(CallFunc_Dummy6)); + addFuncDataACS0( 503, addCallFunc(CallFunc_SetLineRenderStyle)); + addFuncDataACS0( 504, addCallFunc(CallFunc_MapWarp)); + addFuncDataACS0( 505, addCallFunc(CallFunc_AddBot)); + // addFuncDataACS0( 506, addCallFunc(CallFunc_Dummy7)); + addFuncDataACS0( 507, addCallFunc(CallFunc_ExitLevel)); + addFuncDataACS0( 508, addCallFunc(CallFunc_MusicPlay)); + addFuncDataACS0( 509, addCallFunc(CallFunc_MusicStopAll)); + addFuncDataACS0( 510, addCallFunc(CallFunc_MusicRestore)); + // addFuncDataACS0( 511, addCallFunc(CallFunc_Dummy9)); + addFuncDataACS0( 512, addCallFunc(CallFunc_MusicDim)); + + // addFuncDataACS0( 600, addCallFunc(CallFunc_Dummy10)); + // addFuncDataACS0( 601, addCallFunc(CallFunc_Dummy11)); + // addFuncDataACS0( 602, addCallFunc(CallFunc_Dummy12)); + // addFuncDataACS0( 603, addCallFunc(CallFunc_Dummy13)); + // addFuncDataACS0( 604, addCallFunc(CallFunc_Dummy14)); + // addFuncDataACS0( 605, addCallFunc(CallFunc_Dummy15)); + + // addFuncDataACS0( 700, addCallFunc(CallFunc_AddMessage)); + // addFuncDataACS0( 701, addCallFunc(CallFunc_AddMessageForPlayer)); + // addFuncDataACS0( 702, addCallFunc(CallFunc_Dummy16)); + // addFuncDataACS0( 703, addCallFunc(CallFunc_Dummy17)); +} + +ACSVM::Thread *Environment::allocThread() +{ + return new Thread(this); +} + +ACSVM::ModuleName Environment::getModuleName(char const *str, size_t len) +{ + ACSVM::String *name = getString(str, len); + lumpnum_t lump = W_CheckNumForNameInFolder(str, "ACS/"); + + return { name, nullptr, static_cast<size_t>(lump) }; +} + +void Environment::loadModule(ACSVM::Module *module) +{ + ACSVM::ModuleName *const name = &module->name; + + size_t lumpLen = 0; + std::vector<ACSVM::Byte> data; + + if (name->i == (size_t)LUMPERROR) + { + // No lump given for module. + CONS_Alert(CONS_WARNING, "Could not find ACS module \"%s\"; scripts will not function properly!\n", name->s->str); + return; //throw ACSVM::ReadError("file open failure"); + } + + lumpLen = W_LumpLength(name->i); + + if (W_IsLumpWad(name->i) == true || lumpLen == 0) + { + CONS_Debug(DBG_SETUP, "Attempting to load ACS module from the BEHAVIOR lump of map '%s'...\n", name->s->str); + + // The lump given is a virtual resource. + // Try to grab a BEHAVIOR lump from inside of it. + virtres_t *vRes = vres_GetMap(name->i); + auto _ = srb2::finally([vRes]() { vres_Free(vRes); }); + + virtlump_t *vLump = vres_Find(vRes, "BEHAVIOR"); + if (vLump != nullptr && vLump->size > 0) + { + data.insert(data.begin(), vLump->data, vLump->data + vLump->size); + CONS_Debug(DBG_SETUP, "Successfully found BEHAVIOR lump.\n"); + } + else + { + CONS_Debug(DBG_SETUP, "No BEHAVIOR lump found.\n"); + } + } + else + { + CONS_Debug(DBG_SETUP, "Loading ACS module directly from lump '%s'...\n", name->s->str); + + // It's a real lump. + ACSVM::Byte *lump = static_cast<ACSVM::Byte *>(Z_Calloc(lumpLen, PU_STATIC, nullptr)); + auto _ = srb2::finally([lump]() { Z_Free(lump); }); + + W_ReadLump(name->i, lump); + data.insert(data.begin(), lump, lump + lumpLen); + } + + if (data.empty() == false) + { + try + { + module->readBytecode(data.data(), data.size()); + } + catch (const ACSVM::ReadError &e) + { + CONS_Alert(CONS_ERROR, "Failed to load ACS module '%s': %s\n", name->s->str, e.what()); + throw ACSVM::ReadError("failed import"); + } + } + else + { + // Unlike Hexen, a BEHAVIOR lump is not required. + // Simply ignore in this instance. + CONS_Debug(DBG_SETUP, "ACS module has no data, ignoring...\n"); + } +} + +bool Environment::checkTag(ACSVM::Word type, ACSVM::Word tag) +{ + switch (type) + { + case ACS_TAGTYPE_SECTOR: + { + INT32 secnum = -1; + + TAG_ITER_SECTORS(tag, secnum) + { + sector_t *sec = §ors[secnum]; + + if (sec->floordata != nullptr || sec->ceilingdata != nullptr) + { + return false; + } + } + + return true; + } + + case ACS_TAGTYPE_POLYOBJ: + { + const polyobj_t *po = Polyobj_GetForNum(tag); + return (po == nullptr || po->thinker == nullptr); + } + + case ACS_TAGTYPE_CAMERA: + { + const mobj_t *camera = P_FindObjectTypeFromTag(MT_ALTVIEWMAN, tag); + if (camera == nullptr) + { + return true; + } + + return (camera->tracer == nullptr || P_MobjWasRemoved(camera->tracer) == true); + } + } + + return true; +} + +ACSVM::Word Environment::callSpecImpl + ( + ACSVM::Thread *thread, ACSVM::Word spec, + const ACSVM::Word *argV, ACSVM::Word argC + ) +{ + auto info = &static_cast<Thread *>(thread)->info; + ACSVM::MapScope *const map = thread->scopeMap; + + INT32 args[NUM_SCRIPT_ARGS] = {0}; + + char *stringargs[NUM_SCRIPT_STRINGARGS] = {0}; + auto _ = srb2::finally( + [stringargs]() + { + for (int i = 0; i < NUM_SCRIPT_STRINGARGS; i++) + { + Z_Free(stringargs[i]); + } + } + ); + + activator_t *activator = static_cast<activator_t *>(Z_Calloc(sizeof(activator_t), PU_LEVEL, nullptr)); + auto __ = srb2::finally( + [info, activator]() + { + if (info->thread_era == thinker_era) + { + P_SetTarget(&activator->mo, NULL); + Z_Free(activator); + } + } + ); + + int i = 0; + + for (i = 0; i < std::min((signed)(argC), NUM_SCRIPT_STRINGARGS); i++) + { + ACSVM::String *strPtr = map->getString(argV[i]); + + stringargs[i] = static_cast<char *>(Z_Malloc(strPtr->len + 1, PU_STATIC, nullptr)); + M_Memcpy(stringargs[i], strPtr->str, strPtr->len + 1); + } + + for (i = 0; i < std::min((signed)(argC), NUM_SCRIPT_ARGS); i++) + { + args[i] = argV[i]; + } + + P_SetTarget(&activator->mo, info->mo); + activator->line = info->line; + activator->side = info->side; + activator->sector = info->sector; + activator->po = info->po; + activator->fromLineSpecial = false; + + P_ProcessSpecial(activator, spec, args, stringargs); + return 1; +} + +void Environment::printKill(ACSVM::Thread *thread, ACSVM::Word type, ACSVM::Word data) +{ + CONS_Alert(CONS_ERROR, "ACSVM ERROR: Kill %u:%d at %lu\n", type, data, (thread->codePtr - thread->module->codeV.data() - 1)); +} diff --git a/src/acs/environment.hpp b/src/acs/environment.hpp new file mode 100644 index 0000000000000000000000000000000000000000..453df89e5109f12f1e40d21de17d5e6460854629 --- /dev/null +++ b/src/acs/environment.hpp @@ -0,0 +1,49 @@ +// SONIC ROBO BLAST 2 +//----------------------------------------------------------------------------- +// Copyright (C) 2024 by Russell's Smart Interfaces +// Copyright (C) 2024 by Sonic Team Junior. +// Copyright (C) 2016 by James Haley, David Hill, et al. (Team Eternity) +// Copyright (C) 2024 by Sally "TehRealSalt" Cochenour +// Copyright (C) 2024 by Kart Krew +// +// This program is free software distributed under the +// terms of the GNU General Public License, version 2. +// See the 'LICENSE' file for more details. +//----------------------------------------------------------------------------- +/// \file environment.hpp +/// \brief Action Code Script: Environment definition + +#ifndef __SRB2_ACS_ENVIRONMENT_HPP__ +#define __SRB2_ACS_ENVIRONMENT_HPP__ + +#include "acsvm.hpp" + +namespace srb2::acs { + +class Environment : public ACSVM::Environment +{ +public: + Environment(); + + virtual bool checkTag(ACSVM::Word type, ACSVM::Word tag); + + virtual ACSVM::Word callSpecImpl( + ACSVM::Thread *thread, ACSVM::Word spec, + const ACSVM::Word *argV, ACSVM::Word argC + ); + + virtual ACSVM::Thread *allocThread(); + + virtual void printKill(ACSVM::Thread *thread, ACSVM::Word type, ACSVM::Word data); + +protected: + virtual void loadModule(ACSVM::Module *module); + + virtual ACSVM::ModuleName getModuleName(char const *str, std::size_t len); +}; + +} + +extern srb2::acs::Environment ACSEnv; + +#endif // __SRB2_ACS_ENVIRONMENT_HPP__ diff --git a/src/acs/interface.cpp b/src/acs/interface.cpp new file mode 100644 index 0000000000000000000000000000000000000000..deca14f2ce0de081e1a450a699a29788703d21cb --- /dev/null +++ b/src/acs/interface.cpp @@ -0,0 +1,506 @@ +// SONIC ROBO BLAST 2 +//----------------------------------------------------------------------------- +// Copyright (C) 2024 by Russell's Smart Interfaces +// Copyright (C) 2024 by Sonic Team Junior. +// Copyright (C) 2016 by James Haley, David Hill, et al. (Team Eternity) +// Copyright (C) 2024 by Sally "TehRealSalt" Cochenour +// Copyright (C) 2024 by Kart Krew +// +// This program is free software distributed under the +// terms of the GNU General Public License, version 2. +// See the 'LICENSE' file for more details. +//----------------------------------------------------------------------------- +/// \file interface.cpp +/// \brief Action Code Script: Interface for the rest of SRB2's game logic + +#include <algorithm> +#include <cstddef> +#include <istream> +#include <ostream> +#include <vector> + +#include "../tcb_span.hpp" + +#include "acsvm.hpp" + +#include "interface.h" + +#include "../doomtype.h" +#include "../doomdef.h" +#include "../doomstat.h" + +#include "../r_defs.h" +#include "../g_game.h" +#include "../w_wad.h" +#include "../i_system.h" +#include "../p_saveg.h" + +#include "environment.hpp" +#include "thread.hpp" +#include "stream.hpp" + +#include "../cxxutil.hpp" + +using namespace srb2::acs; + +using std::size_t; + +/*-------------------------------------------------- + void ACS_Init(void) + + See header file for description. +--------------------------------------------------*/ +void ACS_Init(void) +{ +#if 0 + // Initialize ACS on engine start-up. + ACSEnv = new Environment(); + I_AddExitFunc(ACS_Shutdown); +#endif +} + +/*-------------------------------------------------- + void ACS_Shutdown(void) + + See header file for description. +--------------------------------------------------*/ +void ACS_Shutdown(void) +{ +#if 0 + // Delete ACS environment. + delete ACSEnv; + ACSEnv = nullptr; +#endif +} + +/*-------------------------------------------------- + void ACS_InvalidateMapScope(size_t mapID) + + See header file for description. +--------------------------------------------------*/ +void ACS_InvalidateMapScope(void) +{ + Environment *env = &ACSEnv; + + ACSVM::GlobalScope *const global = env->getGlobalScope(0); + ACSVM::HubScope *hub = NULL; + ACSVM::MapScope *map = NULL; + + // Conclude hub scope, even if we are not using it. + hub = global->getHubScope(0); + hub->reset(); + + // Conclude current map scope. + map = hub->getMapScope(0); // This is where you'd put in mapID if you add hub support. + map->reset(); +} + +/*-------------------------------------------------- + void ACS_LoadLevelScripts(size_t mapID) + + See header file for description. +--------------------------------------------------*/ +void ACS_LoadLevelScripts(size_t mapID) +{ + Environment *env = &ACSEnv; + + ACSVM::GlobalScope *const global = env->getGlobalScope(0); + ACSVM::HubScope *hub = nullptr; + ACSVM::MapScope *map = nullptr; + + std::vector<ACSVM::Module *> modules; + + // Just some notes on how Hexen's scopes work, if anyone + // intends to implement proper hub logic: + + // The integer is an ID for which hub / map it is, + // and instead sets active according to which ones + // should run, since you can go between them. + + // But I didn't intend on implementing these features, + // since hubs aren't planned for Ring Racers (although + // they might be useful for SRB2), and I intentionally + // avoided implementing global ACS (since Lua would be + // a better language to do that kind of code). + + // Since we literally only are using map scope, we can + // just free everything between every level. But if + // hubs are to be implemented, this logic would need + // to be far more sophisticated. + + // Extra note regarding the commented out ->reset()'s: + // This is too late! That needs to be done before + // PU_LEVEL is purged. Call ACS_InvalidateMapScope + // to take care of that. Those lines are left in + // only as a warning to future code spelunkers. + + // Restart hub scope, even if we are not using it. + hub = global->getHubScope(0); + //hub->reset(); + hub->active = true; + + // Start up new map scope. + map = hub->getMapScope(0); // This is where you'd put in mapID if you add hub support. + //map->reset(); + map->active = true; + + // Insert BEHAVIOR lump into the list. + { + const char *maplumpname = G_BuildMapName(mapID); + + ACSVM::ModuleName name = ACSVM::ModuleName( + env->getString( maplumpname ), + nullptr, + W_CheckNumForMap(maplumpname) + ); + + modules.push_back(env->getModule(name)); + } + + if (modules.empty() == false) + { + // Register the modules with map scope. + map->addModules(modules.data(), modules.size()); + } +} + +/*-------------------------------------------------- + void ACS_RunLevelStartScripts(void) + + See header file for description. +--------------------------------------------------*/ +void ACS_RunLevelStartScripts(void) +{ + Environment *env = &ACSEnv; + + ACSVM::GlobalScope *const global = env->getGlobalScope(0); + ACSVM::HubScope *const hub = global->getHubScope(0); + ACSVM::MapScope *const map = hub->getMapScope(0); + + map->scriptStartType(ACS_ST_OPEN, {}); +} + +/*-------------------------------------------------- + void ACS_RunPlayerRespawnScript(player_t *player) + + See header file for description. +--------------------------------------------------*/ +void ACS_RunPlayerRespawnScript(player_t *player) +{ + Environment *env = &ACSEnv; + + ACSVM::GlobalScope *const global = env->getGlobalScope(0); + ACSVM::HubScope *const hub = global->getHubScope(0); + ACSVM::MapScope *const map = hub->getMapScope(0); + + ACSVM::MapScope::ScriptStartInfo scriptInfo; + ThreadInfo info; + + P_SetTarget(&info.mo, player->mo); + + scriptInfo.info = &info; + + map->scriptStartTypeForced(ACS_ST_RESPAWN, scriptInfo); +} + +/*-------------------------------------------------- + void ACS_RunPlayerDeathScript(player_t *player) + + See header file for description. +--------------------------------------------------*/ +void ACS_RunPlayerDeathScript(player_t *player) +{ + Environment *env = &ACSEnv; + + ACSVM::GlobalScope *const global = env->getGlobalScope(0); + ACSVM::HubScope *const hub = global->getHubScope(0); + ACSVM::MapScope *const map = hub->getMapScope(0); + + ACSVM::MapScope::ScriptStartInfo scriptInfo; + ThreadInfo info; + + P_SetTarget(&info.mo, player->mo); + + scriptInfo.info = &info; + + map->scriptStartTypeForced(ACS_ST_DEATH, scriptInfo); +} + +/*-------------------------------------------------- + void ACS_RunPlayerEnterScript(player_t *player) + + See header file for description. +--------------------------------------------------*/ +void ACS_RunPlayerEnterScript(player_t *player) +{ + Environment *env = &ACSEnv; + + ACSVM::GlobalScope *const global = env->getGlobalScope(0); + ACSVM::HubScope *const hub = global->getHubScope(0); + ACSVM::MapScope *const map = hub->getMapScope(0); + + ACSVM::MapScope::ScriptStartInfo scriptInfo; + ThreadInfo info; + + P_SetTarget(&info.mo, player->mo); + + scriptInfo.info = &info; + + map->scriptStartTypeForced(ACS_ST_ENTER, scriptInfo); +} + +/*-------------------------------------------------- + void ACS_RunPlayerFinishScript(player_t *player) + + See header file for description. +--------------------------------------------------*/ +void ACS_RunPlayerFinishScript(player_t *player) +{ + Environment *env = &ACSEnv; + + ACSVM::GlobalScope *const global = env->getGlobalScope(0); + ACSVM::HubScope *const hub = global->getHubScope(0); + ACSVM::MapScope *const map = hub->getMapScope(0); + + ACSVM::MapScope::ScriptStartInfo scriptInfo; + ThreadInfo info; + + P_SetTarget(&info.mo, player->mo); + + scriptInfo.info = &info; + + map->scriptStartTypeForced(ACS_ST_FINISH, scriptInfo); +} + +/*-------------------------------------------------- + void ACS_RunGameOverScript(void) + + See header file for description. +--------------------------------------------------*/ +void ACS_RunGameOverScript(void) +{ + Environment *env = &ACSEnv; + + ACSVM::GlobalScope *const global = env->getGlobalScope(0); + ACSVM::HubScope *const hub = global->getHubScope(0); + ACSVM::MapScope *const map = hub->getMapScope(0); + + map->scriptStartType(ACS_ST_GAMEOVER, {}); +} + +/*-------------------------------------------------- + void ACS_Tick(void) + + See header file for description. +--------------------------------------------------*/ +void ACS_Tick(void) +{ + Environment *env = &ACSEnv; + + if (env->hasActiveThread() == true) + { + env->exec(); + } +} + +/*-------------------------------------------------- + static std::vector<ACSVM::Word> ACS_MixArgs(tcb::span<const INT32> args, tcb::span<const char* const> stringArgs) + + Convert strings to ACS arguments and position them + correctly among integer arguments. + + Input Arguments:- + args: Integer arguments. + stringArgs: C string arguments. + + Return:- + Final argument vector. +--------------------------------------------------*/ +static std::vector<ACSVM::Word> ACS_MixArgs(tcb::span<const INT32> args, tcb::span<const char* const> stringArgs) +{ + std::vector<ACSVM::Word> argV; + size_t first = std::min(args.size(), stringArgs.size()); + + auto new_string = [env = &ACSEnv](const char* str) -> ACSVM::Word { return ~env->getString(str, strlen(str))->idx; }; + + for (size_t i = 0; i < first; ++i) + { + // args[i] must be 0. + // + // If ACS_Execute is called from ACS, stringargs[i] + // will always be set, because there is no + // differentiation between integers and strings on + // arguments passed to a function. In this case, + // string arguments already exist in the ACS string + // table beforehand (and set in args[i]), so no + // conversion is required here. + // + // If ACS_Execute is called from a map line special, + // args[i] may be left unset (0), while stringArgs[i] + // is set. In this case, conversion to ACS string + // table is necessary. + argV.push_back(!args[i] && stringArgs[i] ? new_string(stringArgs[i]) : args[i]); + } + + for (size_t i = first; i < args.size(); ++i) + { + argV.push_back(args[i]); + } + + for (size_t i = first; i < stringArgs.size(); ++i) + { + argV.push_back(new_string(stringArgs[i] ? stringArgs[i] : "")); + } + + return argV; +} + +/*-------------------------------------------------- + boolean ACS_Execute(const char *name, const INT32 *args, size_t numArgs, const char *const *stringArgs, size_t numStringArgs, activator_t *activator) + + See header file for description. +--------------------------------------------------*/ +boolean ACS_Execute(const char *name, const INT32 *args, size_t numArgs, const char *const *stringArgs, size_t numStringArgs, activator_t *activator) +{ + Environment *env = &ACSEnv; + + ACSVM::GlobalScope *const global = env->getGlobalScope(0); + ACSVM::HubScope *const hub = global->getHubScope(0); + ACSVM::MapScope *const map = hub->getMapScope(0); + ACSVM::ScopeID scope{global->id, hub->id, map->id}; + + ThreadInfo info{activator}; + + ACSVM::String *script = env->getString(name, strlen(name)); + std::vector<ACSVM::Word> argV = ACS_MixArgs(tcb::span {args, numArgs}, tcb::span {stringArgs, numStringArgs}); + return map->scriptStart(script, scope, {argV.data(), argV.size(), &info}); +} + +/*-------------------------------------------------- + boolean ACS_ExecuteAlways(const char *name, const INT32 *args, size_t numArgs, const char *const *stringArgs, size_t numStringArgs, activator_t *activator) + + See header file for description. +--------------------------------------------------*/ +boolean ACS_ExecuteAlways(const char *name, const INT32 *args, size_t numArgs, const char *const *stringArgs, size_t numStringArgs, activator_t *activator) +{ + Environment *env = &ACSEnv; + + ACSVM::GlobalScope *const global = env->getGlobalScope(0); + ACSVM::HubScope *const hub = global->getHubScope(0); + ACSVM::MapScope *const map = hub->getMapScope(0); + ACSVM::ScopeID scope{global->id, hub->id, map->id}; + + ThreadInfo info{activator}; + + ACSVM::String *script = env->getString(name, strlen(name)); + std::vector<ACSVM::Word> argV = ACS_MixArgs(tcb::span {args, numArgs}, tcb::span {stringArgs, numStringArgs}); + return map->scriptStartForced(script, scope, {argV.data(), argV.size(), &info}); +} + +/*-------------------------------------------------- + boolean ACS_ExecuteResult(const char *name, const INT32 *args, size_t numArgs, activator_t *activator) + + See header file for description. +--------------------------------------------------*/ +boolean ACS_ExecuteResult(const char *name, const INT32 *args, size_t numArgs, activator_t *activator) +{ + Environment *env = &ACSEnv; + + ACSVM::GlobalScope *const global = env->getGlobalScope(0); + ACSVM::HubScope *const hub = global->getHubScope(0); + ACSVM::MapScope *const map = hub->getMapScope(0); + + ThreadInfo info{activator}; + + ACSVM::String *script = env->getString(name, strlen(name)); + return map->scriptStartResult(script, {reinterpret_cast<const ACSVM::Word *>(args), numArgs, &info}); +} + +/*-------------------------------------------------- + boolean ACS_Suspend(const char *name) + + See header file for description. +--------------------------------------------------*/ +boolean ACS_Suspend(const char *name) +{ + Environment *env = &ACSEnv; + + ACSVM::GlobalScope *const global = env->getGlobalScope(0); + ACSVM::HubScope *const hub = global->getHubScope(0); + ACSVM::MapScope *const map = hub->getMapScope(0); + ACSVM::ScopeID scope{global->id, hub->id, map->id}; + + ACSVM::String *script = env->getString(name, strlen(name)); + return map->scriptPause(script, scope); +} + +/*-------------------------------------------------- + boolean ACS_Terminate(const char *name) + + See header file for description. +--------------------------------------------------*/ +boolean ACS_Terminate(const char *name) +{ + Environment *env = &ACSEnv; + + ACSVM::GlobalScope *const global = env->getGlobalScope(0); + ACSVM::HubScope *const hub = global->getHubScope(0); + ACSVM::MapScope *const map = hub->getMapScope(0); + ACSVM::ScopeID scope{global->id, hub->id, map->id}; + + ACSVM::String *script = env->getString(name, strlen(name)); + return map->scriptStop(script, scope); +} + +/*-------------------------------------------------- + void ACS_Archive(savebuffer_t *save) + + See header file for description. +--------------------------------------------------*/ +void ACS_Archive(savebuffer_t *save) +{ + Environment *env = &ACSEnv; + + SaveBuffer buffer{save}; + std::ostream stream{&buffer}; + ACSVM::Serial serial{stream}; + + // Enable debug signatures. + serial.signs = true; + + try + { + serial.saveHead(); + env->saveState(serial); + serial.saveTail(); + } + catch (ACSVM::SerialError const &e) + { + I_Error("ACS_Archive: %s\n", e.what()); + } +} + +/*-------------------------------------------------- + void ACS_UnArchive(savebuffer_t *save) + + See header file for description. +--------------------------------------------------*/ +void ACS_UnArchive(savebuffer_t *save) +{ + Environment *env = &ACSEnv; + + SaveBuffer buffer{save}; + std::istream stream{&buffer}; + ACSVM::Serial serial{stream}; + + try + { + serial.loadHead(); + env->loadState(serial); + serial.loadTail(); + } + catch (ACSVM::SerialError const &e) + { + I_Error("ACS_UnArchive: %s\n", e.what()); + } +} diff --git a/src/acs/interface.h b/src/acs/interface.h new file mode 100644 index 0000000000000000000000000000000000000000..bdca8a64486099087ed0643a54596f8d3e92d075 --- /dev/null +++ b/src/acs/interface.h @@ -0,0 +1,321 @@ +// SONIC ROBO BLAST 2 +//----------------------------------------------------------------------------- +// Copyright (C) 2024 by Russell's Smart Interfaces +// Copyright (C) 2024 by Sonic Team Junior. +// Copyright (C) 2016 by James Haley, David Hill, et al. (Team Eternity) +// Copyright (C) 2024 by Sally "TehRealSalt" Cochenour +// Copyright (C) 2024 by Kart Krew +// +// This program is free software distributed under the +// terms of the GNU General Public License, version 2. +// See the 'LICENSE' file for more details. +//----------------------------------------------------------------------------- +/// \file interface.h +/// \brief Action Code Script: Interface for the rest of SRB2's game logic + +#ifndef __SRB2_ACS_INTERFACE_H__ +#define __SRB2_ACS_INTERFACE_H__ + +#include "../doomtype.h" +#include "../doomdef.h" +#include "../doomstat.h" + +#include "../p_spec.h" +#include "../p_saveg.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/*-------------------------------------------------- + void ACS_Init(void); + + Initializes the ACS environment. Handles creating + the VM, initializing its hooks, storing the + pointer for future reference, and adding the + shutdown function. +--------------------------------------------------*/ + +void ACS_Init(void); + + +/*-------------------------------------------------- + void ACS_Shutdown(void); + + Frees the ACS environment, for when the game + is exited. +--------------------------------------------------*/ + +void ACS_Shutdown(void); + + +/*-------------------------------------------------- + void ACS_InvalidateMapScope(size_t mapID); + + Resets the ACS hub and map scopes to remove + existing running scripts, without starting + any new scripts. + + Input Arguments:- + None + + Return:- + None +--------------------------------------------------*/ + +void ACS_InvalidateMapScope(void); + + +/*-------------------------------------------------- + void ACS_LoadLevelScripts(size_t mapID); + + Resets the ACS hub and map scopes to remove + existing running scripts, and inserts the new + level's ACS modules (BEHAVIOR lump) into + the environment. + + Input Arguments:- + mapID: The map's number to read the BEHAVIOR + lump of. + + Return:- + None +--------------------------------------------------*/ + +void ACS_LoadLevelScripts(size_t mapID); + + +/*-------------------------------------------------- + void ACS_RunLevelStartScripts(void); + + Runs the map's special scripts for opening + the level, and for all players to enter + the game. +--------------------------------------------------*/ + +void ACS_RunLevelStartScripts(void); + + +/*-------------------------------------------------- + void ACS_RunPlayerRespawnScript(player_t *player); + + Runs the map's special script for a player + respawning. + + Input Arguments:- + player: The player to run the script for. + + Return:- + None +--------------------------------------------------*/ + +void ACS_RunPlayerRespawnScript(player_t *player); + + +/*-------------------------------------------------- + void ACS_RunPlayerDeathScript(player_t *player); + + Runs the map's special script for a player + dying. + + Input Arguments:- + player: The player to run the script for. + + Return:- + None +--------------------------------------------------*/ + +void ACS_RunPlayerDeathScript(player_t *player); + + +/*-------------------------------------------------- + void ACS_RunPlayerEnterScript(player_t *player); + + Runs the map's special script for a player + entering the game. + + Input Arguments:- + player: The player to run the script for. + + Return:- + None +--------------------------------------------------*/ + +void ACS_RunPlayerEnterScript(player_t *player); + + +/*-------------------------------------------------- + void ACS_RunPlayerFinishScript(player_t *player); + + Runs the map's special script for a player + finishing (P_DoPlayerExit). + + Input Arguments:- + player: The player to run the script for. + + Return:- + None +--------------------------------------------------*/ + +void ACS_RunPlayerFinishScript(player_t *player); + + +/*-------------------------------------------------- + void ACS_RunGameOverScript(void); + + Runs the map's special scripts for exiting + the level, due to a losing condition and + without any extra lives to retry. +--------------------------------------------------*/ + +void ACS_RunGameOverScript(void); + + +/*-------------------------------------------------- + void ACS_Tick(void); + + Executes all of the ACS environment's + currently active threads. +--------------------------------------------------*/ + +void ACS_Tick(void); + + +/*-------------------------------------------------- + boolean ACS_Execute(const INT32 *args, size_t numArgs, const char *const *stringArgs, size_t numStringArgs, activator_t *activator); + + Runs an ACS script by its string name. + Only one instance of the script will run at + a time with this method. + + Input Arguments:- + name: Script string to run. + args: Array of the input arguments. + Strings should be transformed into + ACSVM string IDs. + numArgs: Number of input arguments. + stringArgs: Array of input string arguments. + numStringArgs: Number of input string arguments. + activator: Container for information on what + activated this script. + + Return:- + true if we were able to run the script, otherwise false. +--------------------------------------------------*/ + +boolean ACS_Execute(const char *name, const INT32 *args, size_t numArgs, const char *const *stringArgs, size_t numStringArgs, activator_t *activator); + + +/*-------------------------------------------------- + boolean ACS_ExecuteAlways(const INT32 *args, size_t numArgs, const char *const *stringArgs, size_t numStringArgs, activator_t *activator) + + Runs an ACS script by its string name. + If the script is already running, this method + will create another instance of the script. + (Suspend and Terminate cannot be used, however.) + + Input Arguments:- + name: Script string to run. + args: Array of the input arguments. + Strings should be transformed into + ACSVM string IDs. + numArgs: Number of input arguments. + stringArgs: Array of input string arguments. + numStringArgs: Number of input string arguments. + activator: Container for information on what + activated this script. + + Return:- + true if we were able to run the script, otherwise false. +--------------------------------------------------*/ + +boolean ACS_ExecuteAlways(const char *name, const INT32 *args, size_t numArgs, const char *const *stringArgs, size_t numStringArgs, activator_t *activator); + + +/*-------------------------------------------------- + INT32 ACS_ExecuteResult(const char *name, const INT32 *args, size_t numArgs, activator_t *activator) + + Runs an ACS script by its string name. + Will return the scripts special result + value, if set. + + Input Arguments:- + name: Script string to run. + args: Array of the input arguments. + Strings should be transformed into + ACSVM string IDs. + numArgs: Number of input arguments. + activator: Container for information on what + activated this script. + + Return:- + true if we were able to run the script, otherwise false. +--------------------------------------------------*/ + +INT32 ACS_ExecuteResult(const char *name, const INT32 *args, size_t numArgs, activator_t *activator); + + +/*-------------------------------------------------- + boolean ACS_Suspend(const char *name); + + Pauses an ACS script by its string name. + + Input Arguments:- + name: Script string to pause. + + Return:- + true if we were able to pause the script, otherwise false. +--------------------------------------------------*/ + +boolean ACS_Suspend(const char *name); + + +/*-------------------------------------------------- + boolean ACS_Terminate(const char *name); + + Stops an ACS script by its string name. + + Input Arguments:- + name: Script string to stop. + + Return:- + true if we were able to stop the script, otherwise false. +--------------------------------------------------*/ + +boolean ACS_Terminate(const char *name); + + +/*-------------------------------------------------- + void ACS_Archive(savebuffer_t *save); + + Saves the ACS VM state into a save buffer. + + Input Arguments:- + save: Pointer to the save buffer from P_SaveNetGame. + + Return:- + None +--------------------------------------------------*/ + +void ACS_Archive(savebuffer_t *save); + + +/*-------------------------------------------------- + void ACS_UnArchive(savebuffer_t *save); + + Loads the ACS VM state from a save buffer. + + Input Arguments:- + save: Pointer to the save buffer from P_LoadNetGame. + + Return:- + None +--------------------------------------------------*/ + +void ACS_UnArchive(savebuffer_t *save); + +#ifdef __cplusplus +} +#endif + +#endif // __SRB2_ACS_INTERFACE_H__ diff --git a/src/acs/stream.cpp b/src/acs/stream.cpp new file mode 100644 index 0000000000000000000000000000000000000000..85e070587c6aaf1799f5c069eb975f783759f113 --- /dev/null +++ b/src/acs/stream.cpp @@ -0,0 +1,71 @@ +// SONIC ROBO BLAST 2 +//----------------------------------------------------------------------------- +// Copyright (C) 2024 by Russell's Smart Interfaces +// Copyright (C) 2024 by Sonic Team Junior. +// Copyright (C) 2016 by James Haley, David Hill, et al. (Team Eternity) +// Copyright (C) 2024 by Sally "TehRealSalt" Cochenour +// Copyright (C) 2024 by Kart Krew +// +// This program is free software distributed under the +// terms of the GNU General Public License, version 2. +// See the 'LICENSE' file for more details. +//----------------------------------------------------------------------------- +/// \file stream.cpp +/// \brief Action Code Script: Dummy stream buffer to +/// interact with P_Save/LoadNetGame + +// TODO? Maybe untie this file from ACS? + +#include <istream> +#include <ostream> +#include <streambuf> + +#include "../doomtype.h" +#include "../doomdef.h" +#include "../doomstat.h" + +#include "../p_saveg.h" + +#include "stream.hpp" +#include "../cxxutil.hpp" + +using namespace srb2::acs; + +SaveBuffer::SaveBuffer(savebuffer_t *save_) : + save{save_} +{ +} + +SaveBuffer::int_type SaveBuffer::overflow(SaveBuffer::int_type ch) +{ + if (save->p == save->end) + { + return traits_type::eof(); + } + + *save->p = static_cast<UINT8>(ch); + save->p++; + + return ch; +} + +SaveBuffer::int_type SaveBuffer::underflow() +{ + if (save->p == save->end) + { + return traits_type::eof(); + } + + UINT8 ret = *save->p; + save->p++; + + // Allow the streambuf internal funcs to work + buf[0] = ret; + setg( + reinterpret_cast<SaveBuffer::char_type *>(buf), + reinterpret_cast<SaveBuffer::char_type *>(buf), + reinterpret_cast<SaveBuffer::char_type *>(buf + 1) + ); + + return static_cast<SaveBuffer::int_type>(ret); +} diff --git a/src/acs/stream.hpp b/src/acs/stream.hpp new file mode 100644 index 0000000000000000000000000000000000000000..e18be2c46d24116b3e955d2a77774e5a0364cff0 --- /dev/null +++ b/src/acs/stream.hpp @@ -0,0 +1,50 @@ +// SONIC ROBO BLAST 2 +//----------------------------------------------------------------------------- +// Copyright (C) 2024 by Russell's Smart Interfaces +// Copyright (C) 2024 by Sonic Team Junior. +// Copyright (C) 2016 by James Haley, David Hill, et al. (Team Eternity) +// Copyright (C) 2024 by Sally "TehRealSalt" Cochenour +// Copyright (C) 2024 by Kart Krew +// +// This program is free software distributed under the +// terms of the GNU General Public License, version 2. +// See the 'LICENSE' file for more details. +//----------------------------------------------------------------------------- +/// \file stream.hpp +/// \brief Action Code Script: Dummy stream buffer to +/// interact with P_Save/LoadNetGame + +// TODO? Maybe untie this file from ACS? + +#ifndef __SRB2_ACS_STREAM_HPP__ +#define __SRB2_ACS_STREAM_HPP__ + +#include <streambuf> + +#include "acsvm.hpp" + +extern "C" { +#include "../doomtype.h" +#include "../doomdef.h" +#include "../doomstat.h" +#include "../p_saveg.h" +} + +namespace srb2::acs { + +class SaveBuffer : public std::streambuf +{ +public: + savebuffer_t *save; + UINT8 buf[1]; + + explicit SaveBuffer(savebuffer_t *save_); + +private: + virtual int_type overflow(int_type ch); + virtual int_type underflow(); +}; + +} + +#endif // __SRB2_ACS_STREAM_HPP__ diff --git a/src/acs/thread.cpp b/src/acs/thread.cpp new file mode 100644 index 0000000000000000000000000000000000000000..af47bb2452e9ea8f5616bec3fe64ffce8064007a --- /dev/null +++ b/src/acs/thread.cpp @@ -0,0 +1,94 @@ +// SONIC ROBO BLAST 2 +//----------------------------------------------------------------------------- +// Copyright (C) 2024 by Russell's Smart Interfaces +// Copyright (C) 2024 by Sonic Team Junior. +// Copyright (C) 2016 by James Haley, David Hill, et al. (Team Eternity) +// Copyright (C) 2024 by Sally "TehRealSalt" Cochenour +// Copyright (C) 2024 by Kart Krew +// +// This program is free software distributed under the +// terms of the GNU General Public License, version 2. +// See the 'LICENSE' file for more details. +//----------------------------------------------------------------------------- +/// \file thread.cpp +/// \brief Action Code Script: Thread definition + +#include "acsvm.hpp" + +#include "thread.hpp" + +#include "../doomtype.h" +#include "../doomdef.h" +#include "../doomstat.h" + +#include "../p_saveg.h" +#include "../p_tick.h" +#include "../p_local.h" +#include "../r_defs.h" +#include "../r_state.h" +#include "../p_polyobj.h" + +using namespace srb2::acs; + +void Thread::start( + ACSVM::Script *script, ACSVM::MapScope *map, + const ACSVM::ThreadInfo *infoPtr, const ACSVM::Word *argV, ACSVM::Word argC) +{ + ACSVM::Thread::start(script, map, infoPtr, argV, argC); + + if (infoPtr != nullptr) + { + info = *static_cast<const ThreadInfo *>(infoPtr); + } + else + { + info = {}; + } + + result = 1; +} + +void Thread::stop() +{ + ACSVM::Thread::stop(); + info = {}; +} + +void Thread::saveState(ACSVM::Serial &serial) const +{ + ACSVM::Thread::saveState(serial); + + ACSVM::WriteVLN<size_t>(serial, (info.mo != nullptr && P_MobjWasRemoved(info.mo) == false) ? (info.mo->mobjnum) : 0); + ACSVM::WriteVLN<size_t>(serial, (info.line != nullptr) ? ((info.line - lines) + 1) : 0); + ACSVM::WriteVLN<size_t>(serial, info.side); + ACSVM::WriteVLN<size_t>(serial, (info.sector != nullptr) ? ((info.sector - sectors) + 1) : 0); + ACSVM::WriteVLN<size_t>(serial, (info.po != nullptr) ? ((info.po - PolyObjects) + 1) : 0); +} + +void Thread::loadState(ACSVM::Serial &serial) +{ + ACSVM::Thread::loadState(serial); + + UINT32 temp = static_cast<UINT32>(ACSVM::ReadVLN<size_t>(serial)); + + if (temp != 0) + { + info.mo = nullptr; + + if (P_SetTarget(&info.mo, P_FindNewPosition(temp)) == nullptr) + { + CONS_Debug(DBG_GAMELOGIC, "info.mo not found for ACS thread\n"); // todo: identify which thread + } + } + + size_t lineIndex = ACSVM::ReadVLN<size_t>(serial); + info.line = (lineIndex != 0) ? (&lines[lineIndex - 1]) : nullptr; + + info.side = static_cast<UINT8>(ACSVM::ReadVLN<size_t>(serial)); + + size_t sectorIndex = ACSVM::ReadVLN<size_t>(serial); + info.sector = (sectorIndex != 0) ? (§ors[sectorIndex - 1]) : nullptr; + + size_t polyIndex = ACSVM::ReadVLN<size_t>(serial); + info.po = (polyIndex != 0) ? (&PolyObjects[polyIndex - 1]) : nullptr; +} diff --git a/src/acs/thread.hpp b/src/acs/thread.hpp new file mode 100644 index 0000000000000000000000000000000000000000..1b52780fe47c9c7327a0a2f72457df3a5a923008 --- /dev/null +++ b/src/acs/thread.hpp @@ -0,0 +1,146 @@ +// SONIC ROBO BLAST 2 +//----------------------------------------------------------------------------- +// Copyright (C) 2024 by Russell's Smart Interfaces +// Copyright (C) 2024 by Sonic Team Junior. +// Copyright (C) 2016 by James Haley, David Hill, et al. (Team Eternity) +// Copyright (C) 2024 by Sally "TehRealSalt" Cochenour +// Copyright (C) 2024 by Kart Krew +// +// This program is free software distributed under the +// terms of the GNU General Public License, version 2. +// See the 'LICENSE' file for more details. +//----------------------------------------------------------------------------- +/// \file thread.hpp +/// \brief Action Code Script: Thread definition + +#ifndef __SRB2_ACS_THREAD_HPP__ +#define __SRB2_ACS_THREAD_HPP__ + +#include "acsvm.hpp" + +extern "C" { +#include "../doomtype.h" +#include "../doomdef.h" +#include "../doomstat.h" +#include "../p_tick.h" +#include "../r_defs.h" +#include "../r_state.h" +#include "../p_spec.h" +} + +namespace srb2::acs { + +// +// Special global script types. +// +enum acs_scriptType_e +{ + ACS_ST_OPEN = 1, // OPEN: Runs once when the level starts. + ACS_ST_RESPAWN = 2, // RESPAWN: Runs when a player respawns. + ACS_ST_DEATH = 3, // DEATH: Runs when a player dies. + ACS_ST_ENTER = 4, // ENTER: Runs when a player enters the game; both on start of the level, and when un-spectating. + ACS_ST_GAMEOVER = 5, // GAMEOVER: Runs when the level ends due to a losing condition and no player has an extra life. + ACS_ST_FINISH = 6, // FINISH: Runs when a player finishes +}; + +// +// Script "waiting on tag" types. +// +enum acs_tagType_e +{ + ACS_TAGTYPE_POLYOBJ, + ACS_TAGTYPE_SECTOR, + ACS_TAGTYPE_CAMERA, +}; + +class ThreadInfo : public ACSVM::ThreadInfo +{ +public: + UINT32 thread_era; // If equal to thinker_era, mobj pointers are safe. + mobj_t *mo; // Object that activated this thread. + line_t *line; // Linedef that activated this thread. + UINT8 side; // Front / back side of said linedef. + sector_t *sector; // Sector that activated this thread. + polyobj_t *po; // Polyobject that activated this thread. + bool fromLineSpecial; // Called from P_ProcessLineSpecial. + + ThreadInfo() : + thread_era { thinker_era }, + mo{ nullptr }, + line{ nullptr }, + side{ 0 }, + sector{ nullptr }, + po{ nullptr }, + fromLineSpecial{ false } + { + } + + ThreadInfo(const ThreadInfo &info) : + thread_era { thinker_era }, + mo{ nullptr }, + line{ info.line }, + side{ info.side }, + sector{ info.sector }, + po{ info.po }, + fromLineSpecial{ info.fromLineSpecial } + { + P_SetTarget(&mo, info.mo); + } + + ThreadInfo(const activator_t *activator) : + thread_era { thinker_era }, + mo{ nullptr }, + line{ activator->line }, + side{ activator->side }, + sector{ activator->sector }, + po{ activator->po }, + fromLineSpecial{ static_cast<bool>(activator->fromLineSpecial) } + { + P_SetTarget(&mo, activator->mo); + } + + ~ThreadInfo() + { + if (thread_era == thinker_era) + { + P_SetTarget(&mo, nullptr); + } + } + + ThreadInfo &operator = (const ThreadInfo &info) + { + thread_era = thinker_era; + P_SetTarget(&mo, info.mo); + line = info.line; + side = info.side; + sector = info.sector; + po = info.po; + + return *this; + } +}; + +class Thread : public ACSVM::Thread +{ +public: + ThreadInfo info; + + explicit Thread(ACSVM::Environment *env_) : ACSVM::Thread{env_} {} + + virtual ACSVM::ThreadInfo const *getInfo() const { return &info; } + + virtual void start( + ACSVM::Script *script, ACSVM::MapScope *map, + const ACSVM::ThreadInfo *info, const ACSVM::Word *argV, ACSVM::Word argC + ); + + virtual void stop(); + + virtual void loadState(ACSVM::Serial &serial); + + virtual void saveState(ACSVM::Serial &serial) const; +}; + +} + +#endif // __SRB2_ACS_THREAD_HPP__ diff --git a/src/acs/vm/ACSVM/Action.cpp b/src/acs/vm/ACSVM/Action.cpp new file mode 100644 index 0000000000000000000000000000000000000000..d6aa857ba833c1373239a3f856bef4770feb0648 --- /dev/null +++ b/src/acs/vm/ACSVM/Action.cpp @@ -0,0 +1,90 @@ +//----------------------------------------------------------------------------- +// +// Copyright (C) 2015 David Hill +// +// See COPYING for license information. +// +//----------------------------------------------------------------------------- +// +// Deferred Action classes. +// +//----------------------------------------------------------------------------- + +#include "Action.hpp" + +#include "Environment.hpp" + + +//----------------------------------------------------------------------------| +// Extern Functions | +// + +namespace ACSVM +{ + // + // ScriptAction move constructor + // + ScriptAction::ScriptAction(ScriptAction &&a) : + action{std::move(a.action)}, + argV {std::move(a.argV)}, + id {std::move(a.id)}, + link {this, std::move(a.link)}, + name {std::move(a.name)} + { + } + + // + // ScriptAction constructor + // + ScriptAction::ScriptAction(ScopeID id_, ScriptName name_, Action action_, Vector<Word> &&argV_) : + action{action_}, + argV {std::move(argV_)}, + id {id_}, + link {this}, + name {name_} + { + } + + // + // ScriptAction destructor + // + ScriptAction::~ScriptAction() + { + } + + // + // ScriptAction::lockStrings + // + void ScriptAction::lockStrings(Environment *env) const + { + if(name.s) ++name.s->lock; + + for(auto &arg : argV) + ++env->getString(arg)->lock; + } + + // + // ScriptAction::refStrings + // + void ScriptAction::refStrings(Environment *env) const + { + if(name.s) name.s->ref = true; + + for(auto &arg : argV) + env->getString(arg)->ref = true; + } + + // + // ScriptAction::unlockStrings + // + void ScriptAction::unlockStrings(Environment *env) const + { + if(name.s) --name.s->lock; + + for(auto &arg : argV) + --env->getString(arg)->lock; + } +} + +// EOF + diff --git a/src/acs/vm/ACSVM/Action.hpp b/src/acs/vm/ACSVM/Action.hpp new file mode 100644 index 0000000000000000000000000000000000000000..a2cef539418fd8e60c7dc8b98155da7b600382cc --- /dev/null +++ b/src/acs/vm/ACSVM/Action.hpp @@ -0,0 +1,83 @@ +//----------------------------------------------------------------------------- +// +// Copyright (C) 2015 David Hill +// +// See COPYING for license information. +// +//----------------------------------------------------------------------------- +// +// Deferred Action classes. +// +//----------------------------------------------------------------------------- + +#ifndef ACSVM__Action_H__ +#define ACSVM__Action_H__ + +#include "List.hpp" +#include "Script.hpp" +#include "Vector.hpp" + + +//----------------------------------------------------------------------------| +// Types | +// + +namespace ACSVM +{ + // + // ScopeID + // + class ScopeID + { + public: + ScopeID() = default; + ScopeID(Word global_, Word hub_, Word map_) : + global{global_}, hub{hub_}, map{map_} {} + + bool operator == (ScopeID const &id) const + {return global == id.global && hub == id.hub && map == id.map;} + bool operator != (ScopeID const &id) const + {return global != id.global || hub != id.hub || map != id.map;} + + Word global; + Word hub; + Word map; + }; + + // + // ScriptAction + // + // Represents a deferred Script action. + // + class ScriptAction + { + public: + enum Action + { + Start, + StartForced, + Stop, + Pause, + }; + + + ScriptAction(ScriptAction &&action); + ScriptAction(ScopeID id, ScriptName name, Action action, Vector<Word> &&argV); + ~ScriptAction(); + + void lockStrings(Environment *env) const; + + void refStrings(Environment *env) const; + + void unlockStrings(Environment *env) const; + + Action action; + Vector<Word> argV; + ScopeID id; + ListLink<ScriptAction> link; + ScriptName name; + }; +} + +#endif//ACSVM__Action_H__ + diff --git a/src/acs/vm/ACSVM/Array.cpp b/src/acs/vm/ACSVM/Array.cpp new file mode 100644 index 0000000000000000000000000000000000000000..510f847269482145f2d12f9d6107a83a20687196 --- /dev/null +++ b/src/acs/vm/ACSVM/Array.cpp @@ -0,0 +1,214 @@ +//----------------------------------------------------------------------------- +// +// Copyright (C) 2015-2017 David Hill +// +// See COPYING for license information. +// +//----------------------------------------------------------------------------- +// +// Array class. +// +//----------------------------------------------------------------------------- + +#include "Array.hpp" + +#include "BinaryIO.hpp" +#include "Environment.hpp" +#include "Serial.hpp" + + +//----------------------------------------------------------------------------| +// Static Functions | +// + +namespace ACSVM +{ + // + // FreeData (Word) + // + static void FreeData(Word &) + { + } + + // + // FreeData + // + template<typename T> + static void FreeData(T *&data) + { + if(!data) return; + + for(auto &itr : *data) + FreeData(itr); + + delete[] data; + data = nullptr; + } + + // + // ReadData (Word) + // + static void ReadData(std::istream &in, Word &out) + { + out = ReadVLN<Word>(in); + } + + // + // ReadData + // + template<typename T> + static void ReadData(std::istream &in, T *&out) + { + if(in.get()) + { + if(!out) out = new T[1]{}; + + for(auto &itr : *out) + ReadData(in, itr); + } + else + FreeData(out); + } + + // + // RefStringsData (Word) + // + static void RefStringsData(Environment *env, Word const &data, void (*ref)(String *)) + { + ref(env->getString(data)); + } + + // + // RefStringsData + // + template<typename T> + static void RefStringsData(Environment *env, T *data, void (*ref)(String *)) + { + if(data) for(auto &itr : *data) + RefStringsData(env, itr, ref); + } + + // + // WriteData (Word) + // + static void WriteData(std::ostream &out, Word const &in) + { + WriteVLN(out, in); + } + + // + // WriteData + // + template<typename T> + static void WriteData(std::ostream &out, T *const &in) + { + if(in) + { + out.put('\1'); + + for(auto &itr : *in) + WriteData(out, itr); + } + else + out.put('\0'); + } +} + + +//----------------------------------------------------------------------------| +// Extern Functions | +// + +namespace ACSVM +{ + // + // Array::operator [Word] + // + Word &Array::operator [] (Word idx) + { + if(!data) data = new Data[1]{}; + Bank *&bank = (*data)[idx / (BankSize * SegmSize * PageSize)]; + + if(!bank) bank = new Bank[1]{}; + Segm *&segm = (*bank)[idx / (SegmSize * PageSize) % BankSize]; + + if(!segm) segm = new Segm[1]{}; + Page *&page = (*segm)[idx / PageSize % SegmSize]; + + if(!page) page = new Page[1]{}; + return (*page)[idx % PageSize]; + } + + // + // Array::find + // + Word Array::find(Word idx) const + { + if(!data) return 0; + Bank *&bank = (*data)[idx / (BankSize * SegmSize * PageSize)]; + + if(!bank) return 0; + Segm *&segm = (*bank)[idx / (SegmSize * PageSize) % BankSize]; + + if(!segm) return 0; + Page *&page = (*segm)[idx / PageSize % SegmSize]; + + if(!page) return 0; + return (*page)[idx % PageSize]; + } + + // + // Array::clear + // + void Array::clear() + { + FreeData(data); + } + + // + // Array::loadState + // + void Array::loadState(Serial &in) + { + in.readSign(Signature::Array); + ReadData(in, data); + in.readSign(~Signature::Array); + } + + // + // Array::lockStrings + // + void Array::lockStrings(Environment *env) const + { + RefStringsData(env, data, [](String *s){++s->lock;}); + } + + // + // Array::refStrings + // + void Array::refStrings(Environment *env) const + { + RefStringsData(env, data, [](String *s){s->ref = true;}); + } + + // + // Array::saveState + // + void Array::saveState(Serial &out) const + { + out.writeSign(Signature::Array); + WriteData(out, data); + out.writeSign(~Signature::Array); + } + + // + // Array::unlockStrings + // + void Array::unlockStrings(Environment *env) const + { + RefStringsData(env, data, [](String *s){--s->lock;}); + } +} + +// EOF + diff --git a/src/acs/vm/ACSVM/Array.hpp b/src/acs/vm/ACSVM/Array.hpp new file mode 100644 index 0000000000000000000000000000000000000000..a71f3b58a0d79418a5f24db08fdf36a4808d6fae --- /dev/null +++ b/src/acs/vm/ACSVM/Array.hpp @@ -0,0 +1,71 @@ +//----------------------------------------------------------------------------- +// +// Copyright (C) 2015-2017 David Hill +// +// See COPYING for license information. +// +//----------------------------------------------------------------------------- +// +// Array class. +// +//----------------------------------------------------------------------------- + +#ifndef ACSVM__Array_H__ +#define ACSVM__Array_H__ + +#include "Types.hpp" + + +//----------------------------------------------------------------------------| +// Types | +// + +namespace ACSVM +{ + // + // Array + // + // Sparse-allocation array of 2**32 Words. + // + class Array + { + public: + Array() : data{nullptr} {} + Array(Array const &) = delete; + Array(Array &&array) : data{array.data} {array.data = nullptr;} + ~Array() {clear();} + + Word &operator [] (Word idx); + + void clear(); + + // If idx is allocated, returns that Word. Otherwise, returns 0. + Word find(Word idx) const; + + void loadState(Serial &in); + + void lockStrings(Environment *env) const; + + void refStrings(Environment *env) const; + + void saveState(Serial &out) const; + + void unlockStrings(Environment *env) const; + + private: + static constexpr std::size_t PageSize = 256; + static constexpr std::size_t SegmSize = 256; + static constexpr std::size_t BankSize = 256; + static constexpr std::size_t DataSize = 256; + + using Page = Word [PageSize]; + using Segm = Page*[SegmSize]; + using Bank = Segm*[BankSize]; + using Data = Bank*[DataSize]; + + Data *data; + }; +} + +#endif//ACSVM__Array_H__ + diff --git a/src/acs/vm/ACSVM/BinaryIO.cpp b/src/acs/vm/ACSVM/BinaryIO.cpp new file mode 100644 index 0000000000000000000000000000000000000000..be229bad7550b1f13a30623421ced532412068c0 --- /dev/null +++ b/src/acs/vm/ACSVM/BinaryIO.cpp @@ -0,0 +1,44 @@ +//----------------------------------------------------------------------------- +// +// Copyright (C) 2017 David Hill +// +// See COPYING for license information. +// +//----------------------------------------------------------------------------- +// +// Binary data reading/writing primitives. +// +//----------------------------------------------------------------------------- + +#include "BinaryIO.hpp" + + +//----------------------------------------------------------------------------| +// Extern Functions | +// + +namespace ACSVM +{ + // + // ReadLE4 + // + std::uint_fast32_t ReadLE4(std::istream &in) + { + Byte buf[4]; + for(auto &b : buf) b = in.get(); + return ReadLE4(buf); + } + + // + // WriteLE4 + // + void WriteLE4(std::ostream &out, std::uint_fast32_t in) + { + Byte buf[4]; + WriteLE4(buf, in); + for(auto b : buf) out.put(b); + } +} + +// EOF + diff --git a/src/acs/vm/ACSVM/BinaryIO.hpp b/src/acs/vm/ACSVM/BinaryIO.hpp new file mode 100644 index 0000000000000000000000000000000000000000..56aeea84b07d91c2b793733095c3748d466b859d --- /dev/null +++ b/src/acs/vm/ACSVM/BinaryIO.hpp @@ -0,0 +1,118 @@ +//----------------------------------------------------------------------------- +// +// Copyright (C) 2015-2017 David Hill +// +// See COPYING for license information. +// +//----------------------------------------------------------------------------- +// +// Binary data reading/writing primitives. +// +//----------------------------------------------------------------------------- + +#ifndef ACSVM__BinaryIO_H__ +#define ACSVM__BinaryIO_H__ + +#include "Types.hpp" + +#include <istream> +#include <ostream> +#include <climits> + + +//----------------------------------------------------------------------------| +// Extern Functions | +// + +namespace ACSVM +{ + std::uint_fast8_t ReadLE1(Byte const *data); + std::uint_fast16_t ReadLE2(Byte const *data); + std::uint_fast32_t ReadLE4(Byte const *data); + std::uint_fast32_t ReadLE4(std::istream &in); + + template<typename T> + T ReadVLN(std::istream &in); + + void WriteLE4(Byte *out, std::uint_fast32_t in); + void WriteLE4(std::ostream &out, std::uint_fast32_t in); + + template<typename T> + void WriteVLN(std::ostream &out, T in); + + // + // ReadLE1 + // + inline std::uint_fast8_t ReadLE1(Byte const *data) + { + return static_cast<std::uint_fast8_t>(data[0]); + } + + // + // ReadLE2 + // + inline std::uint_fast16_t ReadLE2(Byte const *data) + { + return + (static_cast<std::uint_fast16_t>(data[0]) << 0) | + (static_cast<std::uint_fast16_t>(data[1]) << 8); + } + + // + // ReadLE4 + // + inline std::uint_fast32_t ReadLE4(Byte const *data) + { + return + (static_cast<std::uint_fast32_t>(data[0]) << 0) | + (static_cast<std::uint_fast32_t>(data[1]) << 8) | + (static_cast<std::uint_fast32_t>(data[2]) << 16) | + (static_cast<std::uint_fast32_t>(data[3]) << 24); + } + + // + // ReadVLN + // + template<typename T> + T ReadVLN(std::istream &in) + { + T out{0}; + + unsigned char c; + while(((c = in.get()) & 0x80) && in) + out = (out << 7) + (c & 0x7F); + out = (out << 7) + c; + + return out; + } + + // + // WriteLE4 + // + inline void WriteLE4(Byte *out, std::uint_fast32_t in) + { + out[0] = (in >> 0) & 0xFF; + out[1] = (in >> 8) & 0xFF; + out[2] = (in >> 16) & 0xFF; + out[3] = (in >> 24) & 0xFF; + } + + // + // WriteVLN + // + template<typename T> + void WriteVLN(std::ostream &out, T in) + { + constexpr std::size_t len = (sizeof(T) * CHAR_BIT + 6) / 7; + char buf[len], *ptr = buf + len; + + *--ptr = static_cast<char>(in & 0x7F); + while((in >>= 7)) + *--ptr = static_cast<char>(in & 0x7F) | 0x80; + + out.write(ptr, (buf + len) - ptr); + } +} + +#endif//ACSVM__BinaryIO_H__ + diff --git a/src/acs/vm/ACSVM/CMakeLists.txt b/src/acs/vm/ACSVM/CMakeLists.txt new file mode 100644 index 0000000000000000000000000000000000000000..29760733fb97ed9ff32d6a74f6c947f1a8500678 --- /dev/null +++ b/src/acs/vm/ACSVM/CMakeLists.txt @@ -0,0 +1,82 @@ +##----------------------------------------------------------------------------- +## +## Copyright (C) 2015-2017 David Hill +## +## See COPYING for license information. +## +##----------------------------------------------------------------------------- +## +## CMake file for acsvm. +## +##----------------------------------------------------------------------------- + + +##----------------------------------------------------------------------------| +## Environment Configuration | +## + +include_directories(.) + + +##----------------------------------------------------------------------------| +## Targets | +## + +## +## acsvm +## +add_library(acsvm ${ACSVM_SHARED_DECL} + Action.cpp + Action.hpp + Array.cpp + Array.hpp + BinaryIO.cpp + BinaryIO.hpp + CallFunc.cpp + CallFunc.hpp + Code.hpp + CodeData.cpp + CodeData.hpp + CodeList.hpp + Environment.cpp + Environment.hpp + Error.cpp + Error.hpp + Function.cpp + Function.hpp + HashMap.hpp + HashMapFixed.hpp + ID.hpp + Init.cpp + Init.hpp + Jump.cpp + Jump.hpp + Module.cpp + Module.hpp + ModuleACS0.cpp + ModuleACSE.cpp + PrintBuf.cpp + PrintBuf.hpp + Scope.cpp + Scope.hpp + Script.cpp + Script.hpp + Serial.cpp + Serial.hpp + Stack.hpp + Store.hpp + String.cpp + String.hpp + Thread.cpp + Thread.hpp + ThreadExec.cpp + Tracer.cpp + Tracer.hpp + Types.hpp + Vector.hpp +) + +ACSVM_INSTALL_LIB(acsvm) + +## EOF + diff --git a/src/acs/vm/ACSVM/CallFunc.cpp b/src/acs/vm/ACSVM/CallFunc.cpp new file mode 100644 index 0000000000000000000000000000000000000000..142865e2da71c62b5f8c86985e20cc4e398ea288 --- /dev/null +++ b/src/acs/vm/ACSVM/CallFunc.cpp @@ -0,0 +1,480 @@ +//----------------------------------------------------------------------------- +// +// Copyright (C) 2015 David Hill +// +// See COPYING for license information. +// +//----------------------------------------------------------------------------- +// +// Internal CallFunc functions. +// +//----------------------------------------------------------------------------- + +#include "CallFunc.hpp" + +#include "Action.hpp" +#include "Code.hpp" +#include "Environment.hpp" +#include "Module.hpp" +#include "Scope.hpp" +#include "Thread.hpp" + +#include <cctype> +#include <cinttypes> + + +//----------------------------------------------------------------------------| +// Static Functions | +// + +namespace ACSVM +{ + // + // PrintArray + // + static void PrintArray(Thread *thread, Word const *argv, Word argc, Array const &arr) + { + Word idx = argv[0] + (argc > 2 ? argv[2] : 0); + Word len = argc > 3 ? argv[3] : -1; + + thread->env->printArray(thread->printBuf, arr, idx, len); + } + + // + // StrCaseCmp + // + static int StrCaseCmp(String *l, String *r, Word n) + { + for(char const *ls = l->str, *rs = r->str;; ++ls, ++rs) + { + char lc = std::toupper(*ls), rc = std::toupper(*rs); + if(lc != rc) return lc < rc ? -1 : 1; + if(!lc || !n--) return 0; + } + } + + // + // StrCmp + // + static int StrCmp(String *l, String *r, Word n) + { + for(char const *ls = l->str, *rs = r->str;; ++ls, ++rs) + { + char lc = *ls, rc = *rs; + if(lc != rc) return lc < rc ? -1 : 1; + if(!lc || !n--) return 0; + } + } + + // + // StrCpyArray + // + static bool StrCpyArray(Thread *thread, Word const *argv, Array &dst) + { + Word dstOff = argv[0] + argv[2]; + Word dstLen = argv[3]; + String *src = thread->scopeMap->getString(argv[4]); + Word srcIdx = argv[5]; + + if(srcIdx > src->len) return false; + + for(Word dstIdx = dstOff;;) + { + if(dstIdx - dstOff == dstLen) return false; + if(!(dst[dstIdx++] = src->str[srcIdx++])) return true; + } + } +} + + +//----------------------------------------------------------------------------| +// Extern Functions | +// + +namespace ACSVM +{ + // + // void Nop() + // + bool CallFunc_Func_Nop(Thread *, Word const *, Word) + { + return false; + } + + // + // [[noreturn]] void Kill() + // + bool CallFunc_Func_Kill(Thread *thread, Word const *, Word) + { + thread->env->printKill(thread, static_cast<Word>(KillType::UnknownFunc), 0); + thread->state = ThreadState::Stopped; + return true; + } + + //====================================================== + // Printing Functions + // + + // + // void PrintChar(char c) + // + bool CallFunc_Func_PrintChar(Thread *thread, Word const *argv, Word) + { + thread->printBuf.reserve(1); + thread->printBuf.put(static_cast<char>(argv[0])); + return false; + } + + // + // str PrintEndStr() + // + bool CallFunc_Func_PrintEndStr(Thread *thread, Word const *, Word) + { + char const *data = thread->printBuf.data(); + std::size_t size = thread->printBuf.size(); + String *str = thread->env->getString(data, size); + thread->printBuf.drop(); + thread->dataStk.push(~str->idx); + return false; + } + + // + // void PrintFixD(fixed d) + // + bool CallFunc_Func_PrintFixD(Thread *thread, Word const *argv, Word) + { + // %E worst case: -3.276800e+04 == 13 + // %F worst case: -32767.999985 == 13 + // %G worst case: -1.52588e-05 == 12 + // %G should be maximally P+6 + extra exponent digits. + thread->printBuf.reserve(12); + thread->printBuf.format("%G", static_cast<std::int32_t>(argv[0]) / 65536.0); + return false; + } + + // + // void PrintGblArr(int idx, int arr, int off = 0, int len = -1) + // + bool CallFunc_Func_PrintGblArr(Thread *thread, Word const *argv, Word argc) + { + PrintArray(thread, argv, argc, thread->scopeGbl->arrV[argv[1]]); + return false; + } + + // + // void PrintHubArr(int idx, int arr, int off = 0, int len = -1) + // + bool CallFunc_Func_PrintHubArr(Thread *thread, Word const *argv, Word argc) + { + PrintArray(thread, argv, argc, thread->scopeHub->arrV[argv[1]]); + return false; + } + + // + // void PrintIntB(int b) + // + bool CallFunc_Func_PrintIntB(Thread *thread, Word const *argv, Word) + { + // %B worst case: 11111111111111111111111111111111 == 32 + char buf[32], *end = buf+32, *itr = end; + for(Word b = argv[0]; b; b >>= 1) *--itr = '0' + (b & 1); + thread->printBuf.reserve(end - itr); + thread->printBuf.put(itr, end - itr); + return false; + } + + // + // void PrintIntD(int d) + // + bool CallFunc_Func_PrintIntD(Thread *thread, Word const *argv, Word) + { + // %d worst case: -2147483648 == 11 + thread->printBuf.reserve(11); + thread->printBuf.format("%" PRId32, static_cast<std::int32_t>(argv[0])); + return false; + } + + // + // void PrintIntX(int x) + // + bool CallFunc_Func_PrintIntX(Thread *thread, Word const *argv, Word) + { + // %d worst case: FFFFFFFF == 8 + thread->printBuf.reserve(8); + thread->printBuf.format("%" PRIX32, static_cast<std::uint32_t>(argv[0])); + return false; + } + + // + // void PrintLocArr(int idx, int arr, int off = 0, int len = -1) + // + bool CallFunc_Func_PrintLocArr(Thread *thread, Word const *argv, Word argc) + { + PrintArray(thread, argv, argc, thread->localArr[argv[1]]); + return false; + } + + // + // void PrintModArr(int idx, int arr, int off = 0, int len = -1) + // + bool CallFunc_Func_PrintModArr(Thread *thread, Word const *argv, Word argc) + { + PrintArray(thread, argv, argc, *thread->scopeMod->arrV[argv[1]]); + return false; + } + + // + // void PrintPush() + // + bool CallFunc_Func_PrintPush(Thread *thread, Word const *, Word) + { + thread->printBuf.push(); + return false; + } + + // + // void PrintString(str s) + // + bool CallFunc_Func_PrintString(Thread *thread, Word const *argv, Word) + { + String *s = thread->scopeMap->getString(argv[0]); + thread->printBuf.reserve(s->len0); + thread->printBuf.put(s->str, s->len0); + return false; + } + + //====================================================== + // Script Functions + // + + // + // int ScrPauseS(str name, int map) + // + bool CallFunc_Func_ScrPauseS(Thread *thread, Word const *argV, Word) + { + String *name = thread->scopeMap->getString(argV[0]); + ScopeID scope{thread->scopeGbl->id, thread->scopeHub->id, argV[1]}; + if(!scope.map) scope.map = thread->scopeMap->id; + + thread->dataStk.push(thread->scopeMap->scriptPause(name, scope)); + return false; + } + + // + // int ScrStartS(str name, int map, ...) + // + bool CallFunc_Func_ScrStartS(Thread *thread, Word const *argV, Word argC) + { + String *name = thread->scopeMap->getString(argV[0]); + ScopeID scope{thread->scopeGbl->id, thread->scopeHub->id, argV[1]}; + if(!scope.map) scope.map = thread->scopeMap->id; + + thread->dataStk.push(thread->scopeMap->scriptStart(name, scope, {argV+2, argC-2})); + return false; + } + + // + // int ScrStartSD(str name, int map, int arg0, int arg1, int lock) + // + bool CallFunc_Func_ScrStartSD(Thread *thread, Word const *argV, Word) + { + if(!thread->env->checkLock(thread, argV[4], true)) + { + thread->dataStk.push(0); + return false; + } + + return CallFunc_Func_ScrStartS(thread, argV, 4); + } + + // + // int ScrStartSF(str name, int map, ...) + // + bool CallFunc_Func_ScrStartSF(Thread *thread, Word const *argV, Word argC) + { + String *name = thread->scopeMap->getString(argV[0]); + ScopeID scope{thread->scopeGbl->id, thread->scopeHub->id, argV[1]}; + if(!scope.map) scope.map = thread->scopeMap->id; + + thread->dataStk.push(thread->scopeMap->scriptStartForced(name, scope, {argV+2, argC-2})); + return false; + } + + // + // int ScrStartSL(str name, int map, int arg0, int arg1, int lock) + // + bool CallFunc_Func_ScrStartSL(Thread *thread, Word const *argV, Word) + { + if(!thread->env->checkLock(thread, argV[4], false)) + { + thread->dataStk.push(0); + return false; + } + + return CallFunc_Func_ScrStartS(thread, argV, 4); + } + + // + // int ScrStartSR(str name, ...) + // + bool CallFunc_Func_ScrStartSR(Thread *thread, Word const *argV, Word argC) + { + String *name = thread->scopeMap->getString(argV[0]); + + thread->dataStk.push(thread->scopeMap->scriptStartResult(name, {argV+1, argC-1})); + return false; + } + + // + // int ScrStopS(str name, int map) + // + bool CallFunc_Func_ScrStopS(Thread *thread, Word const *argV, Word) + { + String *name = thread->scopeMap->getString(argV[0]); + ScopeID scope{thread->scopeGbl->id, thread->scopeHub->id, argV[1]}; + if(!scope.map) scope.map = thread->scopeMap->id; + + thread->dataStk.push(thread->scopeMap->scriptStop(name, scope)); + return false; + } + + //====================================================== + // String Functions + // + + // + // int GetChar(str s, int i) + // + bool CallFunc_Func_GetChar(Thread *thread, Word const *argv, Word) + { + thread->dataStk.push(thread->scopeMap->getString(argv[0])->get(argv[1])); + return false; + } + + // + // int StrCaseCmp(str l, str r, int n = -1) + // + bool CallFunc_Func_StrCaseCmp(Thread *thread, Word const *argv, Word argc) + { + String *l = thread->scopeMap->getString(argv[0]); + String *r = thread->scopeMap->getString(argv[1]); + Word n = argc > 2 ? argv[2] : -1; + + thread->dataStk.push(StrCaseCmp(l, r, n)); + return false; + } + + // + // int StrCmp(str l, str r, int n = -1) + // + bool CallFunc_Func_StrCmp(Thread *thread, Word const *argv, Word argc) + { + String *l = thread->scopeMap->getString(argv[0]); + String *r = thread->scopeMap->getString(argv[1]); + Word n = argc > 2 ? argv[2] : -1; + + thread->dataStk.push(StrCmp(l, r, n)); + return false; + } + + // + // int StrCpyGblArr(int idx, int dst, int dstOff, int dstLen, str src, int srcOff) + // + bool CallFunc_Func_StrCpyGblArr(Thread *thread, Word const *argv, Word) + { + thread->dataStk.push(StrCpyArray(thread, argv, thread->scopeGbl->arrV[argv[1]])); + return false; + } + + // + // int StrCpyHubArr(int idx, int dst, int dstOff, int dstLen, str src, int srcOff) + // + bool CallFunc_Func_StrCpyHubArr(Thread *thread, Word const *argv, Word) + { + thread->dataStk.push(StrCpyArray(thread, argv, thread->scopeHub->arrV[argv[1]])); + return false; + } + + // + // int StrCpyLocArr(int idx, int dst, int dstOff, int dstLen, str src, int srcOff) + // + bool CallFunc_Func_StrCpyLocArr(Thread *thread, Word const *argv, Word) + { + thread->dataStk.push(StrCpyArray(thread, argv, thread->localArr[argv[1]])); + return false; + } + + // + // int StrCpyModArr(int idx, int dst, int dstOff, int dstLen, str src, int srcOff) + // + bool CallFunc_Func_StrCpyModArr(Thread *thread, Word const *argv, Word) + { + thread->dataStk.push(StrCpyArray(thread, argv, *thread->scopeMod->arrV[argv[1]])); + return false; + } + + // + // str StrLeft(str s, int len) + // + bool CallFunc_Func_StrLeft(Thread *thread, Word const *argv, Word) + { + String *str = thread->scopeMap->getString(argv[0]); + Word len = argv[1]; + + if(len < str->len) + str = thread->env->getString(str->str, len); + + thread->dataStk.push(~str->idx); + return false; + } + + // + // int StrLen(str s) + // + bool CallFunc_Func_StrLen(Thread *thread, Word const *argv, Word) + { + thread->dataStk.push(thread->scopeMap->getString(argv[0])->len0); + return false; + } + + // + // str StrMid(str s, int idx, int len) + // + bool CallFunc_Func_StrMid(Thread *thread, Word const *argv, Word) + { + String *str = thread->scopeMap->getString(argv[0]); + Word idx = argv[1]; + Word len = argv[2]; + + if(idx < str->len) + { + if(len < str->len - idx) + str = thread->env->getString(str->str + idx, len); + else + str = thread->env->getString(str->str + idx, str->len - idx); + } + else + str = thread->env->getString("", static_cast<std::size_t>(0)); + + thread->dataStk.push(~str->idx); + return false; + } + + // + // str StrRight(str s, int len) + // + bool CallFunc_Func_StrRight(Thread *thread, Word const *argv, Word) + { + String *str = thread->scopeMap->getString(argv[0]); + Word len = argv[1]; + + if(len < str->len) + str = thread->env->getString(str->str + str->len - len, len); + + thread->dataStk.push(~str->idx); + return false; + } +} + +// EOF + diff --git a/src/acs/vm/ACSVM/CallFunc.hpp b/src/acs/vm/ACSVM/CallFunc.hpp new file mode 100644 index 0000000000000000000000000000000000000000..8813e85462a93546ae1f327d77df1b4e528160d4 --- /dev/null +++ b/src/acs/vm/ACSVM/CallFunc.hpp @@ -0,0 +1,31 @@ +//----------------------------------------------------------------------------- +// +// Copyright (C) 2015 David Hill +// +// See COPYING for license information. +// +//----------------------------------------------------------------------------- +// +// Internal CallFunc functions. +// +//----------------------------------------------------------------------------- + +#ifndef ACSVM__CallFunc_H__ +#define ACSVM__CallFunc_H__ + +#include "Types.hpp" + + +//----------------------------------------------------------------------------| +// Extern Functions | +// + +namespace ACSVM +{ + #define ACSVM_FuncList(name) \ + bool (CallFunc_Func_##name)(Thread *thread, Word const *argv, Word argc); + #include "CodeList.hpp" +} + +#endif//ACSVM__CallFunc_H__ + diff --git a/src/acs/vm/ACSVM/Code.hpp b/src/acs/vm/ACSVM/Code.hpp new file mode 100644 index 0000000000000000000000000000000000000000..a3b16cc7617eda1f06d8fbf272046bf7d1852082 --- /dev/null +++ b/src/acs/vm/ACSVM/Code.hpp @@ -0,0 +1,91 @@ +//----------------------------------------------------------------------------- +// +// Copyright (C) 2015 David Hill +// +// See COPYING for license information. +// +//----------------------------------------------------------------------------- +// +// Code classes. +// +//----------------------------------------------------------------------------- + +#ifndef ACSVM__Code_H__ +#define ACSVM__Code_H__ + +#include "Types.hpp" + + +//----------------------------------------------------------------------------| +// Types | +// + +namespace ACSVM +{ + // + // Code + // + // Internal codes. + // + enum class Code + { + #define ACSVM_CodeList(name, ...) name, + #include "CodeList.hpp" + + None + }; + + // + // CodeACS0 + // + // ACS0 codes. + // + enum class CodeACS0 + { + #define ACSVM_CodeListACS0(name, idx, ...) name = idx, + #include "CodeList.hpp" + + None + }; + + // + // Func + // + // Internal CallFunc indexes. + // + enum class Func + { + #define ACSVM_FuncList(name) name, + #include "CodeList.hpp" + + None + }; + + // + // FuncACS0 + // + // ACS0 CallFunc indexes. + // + enum class FuncACS0 + { + #define ACSVM_FuncListACS0(name, idx, ...) name = idx, + #include "CodeList.hpp" + + None + }; + + // + // KillType + // + enum class KillType + { + None, + OutOfBounds, + UnknownCode, + UnknownFunc, + BranchLimit, + }; +} + +#endif//ACSVM__Code_H__ + diff --git a/src/acs/vm/ACSVM/CodeData.cpp b/src/acs/vm/ACSVM/CodeData.cpp new file mode 100644 index 0000000000000000000000000000000000000000..5a05ed4ae07b657374cada59e5f93feb18204a42 --- /dev/null +++ b/src/acs/vm/ACSVM/CodeData.cpp @@ -0,0 +1,205 @@ +//----------------------------------------------------------------------------- +// +// Copyright (C) 2015 David Hill +// +// See COPYING for license information. +// +//----------------------------------------------------------------------------- +// +// CodeData classes. +// +//----------------------------------------------------------------------------- + +#include "CodeData.hpp" + +#include "Code.hpp" + +#include <algorithm> +#include <cstring> + + +//----------------------------------------------------------------------------| +// Extern Functions | +// + +namespace ACSVM +{ + // + // CodeDataACS0 constructor + // + CodeDataACS0::CodeDataACS0(char const *args_, Code transCode_, + Word stackArgC_, Word transFunc_) : + code {CodeACS0::None}, + args {args_}, + argc {CountArgs(args_)}, + stackArgC{stackArgC_}, + transCode{transCode_}, + transFunc{transFunc_} + { + } + + // + // CodeDataACS0 constructor + // + CodeDataACS0::CodeDataACS0(char const *args_, Word stackArgC_, Word transFunc_) : + code {CodeACS0::None}, + args {args_}, + argc {CountArgs(args_)}, + stackArgC{stackArgC_}, + transCode{argc ? Code::CallFunc_Lit : Code::CallFunc}, + transFunc{transFunc_} + { + } + + // + // CodeDataACS0 constructor + // + CodeDataACS0::CodeDataACS0(CodeACS0 code_, char const *args_, + Code transCode_, Word stackArgC_, Func transFunc_) : + code {code_}, + args {args_}, + argc {CountArgs(args_)}, + stackArgC{stackArgC_}, + transCode{transCode_}, + transFunc{transFunc_ != Func::None ? static_cast<Word>(transFunc_) : 0} + { + } + + // + // CodeDataACS0::CountArgs + // + std::size_t CodeDataACS0::CountArgs(char const *args) + { + std::size_t argc = 0; + + for(; *args; ++args) switch(*args) + { + case 'B': + case 'H': + case 'W': + case 'b': + case 'h': + ++argc; + break; + } + + return argc; + } + + // + // FuncDataACS0 copy constructor + // + FuncDataACS0::FuncDataACS0(FuncDataACS0 const &data) : + transFunc{data.transFunc}, + + transCodeV{new TransCode[data.transCodeC]}, + transCodeC{data.transCodeC} + { + std::copy(data.transCodeV, data.transCodeV + transCodeC, transCodeV); + } + + // + // FuncDataACS0 move constructor + // + FuncDataACS0::FuncDataACS0(FuncDataACS0 &&data) : + transFunc{data.transFunc}, + + transCodeV{data.transCodeV}, + transCodeC{data.transCodeC} + { + data.transCodeV = nullptr; + data.transCodeC = 0; + } + + // + // FuncDataACS0 constructor + // + FuncDataACS0::FuncDataACS0(FuncACS0 func_, Func transFunc_, + std::initializer_list<TransCode> transCodes) : + func{func_}, + + transFunc{transFunc_ != Func::None ? static_cast<Word>(transFunc_) : 0}, + + transCodeV{new TransCode[transCodes.size()]}, + transCodeC{transCodes.size()} + { + std::copy(transCodes.begin(), transCodes.end(), transCodeV); + } + + // + // FuncDataACS0 constructor + // + FuncDataACS0::FuncDataACS0(Word transFunc_) : + func{FuncACS0::None}, + + transFunc{transFunc_}, + + transCodeV{nullptr}, + transCodeC{0} + { + } + + // + // FuncDataACS0 constructor + // + FuncDataACS0::FuncDataACS0(Word transFunc_, + std::initializer_list<TransCode> transCodes) : + func{FuncACS0::None}, + + transFunc{transFunc_}, + + transCodeV{new TransCode[transCodes.size()]}, + transCodeC{transCodes.size()} + { + std::copy(transCodes.begin(), transCodes.end(), transCodeV); + } + + // + // FuncDataACS0 constructor + // + FuncDataACS0::FuncDataACS0(Word transFunc_, + std::unique_ptr<TransCode[]> &&transCodeV_, std::size_t transCodeC_) : + func{FuncACS0::None}, + + transFunc{transFunc_}, + + transCodeV{transCodeV_.release()}, + transCodeC{transCodeC_} + { + } + + // + // FuncDataACS0 destructor + // + FuncDataACS0::~FuncDataACS0() + { + delete[] transCodeV; + } + + // + // FuncDataACS0::operator = FuncDataACS0 + // + FuncDataACS0 &FuncDataACS0::operator = (FuncDataACS0 &&data) + { + std::swap(transFunc, data.transFunc); + + std::swap(transCodeV, data.transCodeV); + std::swap(transCodeC, data.transCodeC); + + return *this; + } + + // + // FuncDataACS0::getTransCode + // + Code FuncDataACS0::getTransCode(Word argc) const + { + for(auto itr = transCodeV, end = itr + transCodeC; itr != end; ++itr) + if(itr->first == argc) return itr->second; + + return Code::CallFunc; + } +} + +// EOF + diff --git a/src/acs/vm/ACSVM/CodeData.hpp b/src/acs/vm/ACSVM/CodeData.hpp new file mode 100644 index 0000000000000000000000000000000000000000..71b6f635afeb61483496229d8a5baaf7c69a07ca --- /dev/null +++ b/src/acs/vm/ACSVM/CodeData.hpp @@ -0,0 +1,132 @@ +//----------------------------------------------------------------------------- +// +// Copyright (C) 2015 David Hill +// +// See COPYING for license information. +// +//----------------------------------------------------------------------------- +// +// CodeData classes. +// +//----------------------------------------------------------------------------- + +#ifndef ACSVM__CodeData_H__ +#define ACSVM__CodeData_H__ + +#include "Types.hpp" + +#include <initializer_list> +#include <memory> +#include <utility> + + +//----------------------------------------------------------------------------| +// Types | +// + +namespace ACSVM +{ + // + // CodeData + // + // Internal code description. + // + class CodeData + { + public: + Code code; + + Word argc; + }; + + // + // CodeDataACS0 + // + // ACS0 code description. + // + class CodeDataACS0 + { + public: + CodeDataACS0(char const *args, Code transCode, Word stackArgC, + Word transFunc = 0); + CodeDataACS0(char const *args, Word stackArgC, Word transFunc); + CodeDataACS0(CodeACS0 code, char const *args, Code transCode, + Word stackArgC, Func transFunc); + + // Code index. If not an internally recognized code, is set to None. + CodeACS0 code; + + // String describing the code's arguments. + // A - Previous value is MapReg index. + // a - Previous Value is MapArr index. + // B - Single byte. + // b - Single byte if compressed, full word otherwise. + // G - Previous value is GblReg index. + // g - Previous Value is GblArr index. + // H - Half word. + // h - Half word if compressed, full word otherwise. + // J - Previous value is jump index. + // L - Previous value is LocReg index. + // l - Previous Value is LocArr index. + // O - Previous value is ModReg index. + // o - Previous Value is ModArr index. + // S - Previous value is string index. + // U - Previous value is HubReg index. + // u - Previous Value is HubArr index. + // W - Full word. + char const *args; + std::size_t argc; + + // Stack argument count. + Word stackArgC; + + // Internal code to translate to. + Code transCode; + + // CallFunc index to translate to. + Word transFunc; + + private: + static std::size_t CountArgs(char const *args); + }; + + // + // FuncDataACS0 + // + // ACS0 CallFunc description. + // + class FuncDataACS0 + { + public: + using TransCode = std::pair<Word, Code>; + + FuncDataACS0(FuncDataACS0 const &); + FuncDataACS0(FuncDataACS0 &&data); + FuncDataACS0(FuncACS0 func, Func transFunc, + std::initializer_list<TransCode> transCodes); + FuncDataACS0(Word transFunc); + FuncDataACS0(Word transFunc, std::initializer_list<TransCode> transCodes); + FuncDataACS0(Word transFunc, std::unique_ptr<TransCode[]> &&transCodeV, + std::size_t transCodeC); + ~FuncDataACS0(); + + FuncDataACS0 &operator = (FuncDataACS0 const &) = delete; + FuncDataACS0 &operator = (FuncDataACS0 &&data); + + // Internal code to translate to. + Code getTransCode(Word argc) const; + + // CallFunc index. If not internally recognized, is set to None. + FuncACS0 func; + + // CallFunc index to translate to. + Word transFunc; + + private: + TransCode *transCodeV; + std::size_t transCodeC; + }; +} + +#endif//ACSVM__CodeData_H__ + diff --git a/src/acs/vm/ACSVM/CodeList.hpp b/src/acs/vm/ACSVM/CodeList.hpp new file mode 100644 index 0000000000000000000000000000000000000000..7bc3cb009fb2fa9de81ffbf3344e362edf0542f3 --- /dev/null +++ b/src/acs/vm/ACSVM/CodeList.hpp @@ -0,0 +1,455 @@ +//----------------------------------------------------------------------------- +// +// Copyright (C) 2015 David Hill +// +// See COPYING for license information. +// +//----------------------------------------------------------------------------- +// +// List of all codes. +// +//----------------------------------------------------------------------------- + + +#ifdef ACSVM_CodeList + +ACSVM_CodeList(Nop, 0) +ACSVM_CodeList(Kill, 2) + +// Binary operator codes. +#define ACSVM_CodeList_BinaryOpSet(name) \ + ACSVM_CodeList(name, 0) \ + ACSVM_CodeList(name##_GblArr, 1) \ + ACSVM_CodeList(name##_GblReg, 1) \ + ACSVM_CodeList(name##_HubArr, 1) \ + ACSVM_CodeList(name##_HubReg, 1) \ + ACSVM_CodeList(name##_LocArr, 1) \ + ACSVM_CodeList(name##_LocReg, 1) \ + ACSVM_CodeList(name##_ModArr, 1) \ + ACSVM_CodeList(name##_ModReg, 1) +ACSVM_CodeList_BinaryOpSet(AddU) +ACSVM_CodeList_BinaryOpSet(AndU) +ACSVM_CodeList_BinaryOpSet(DivI) +ACSVM_CodeList_BinaryOpSet(ModI) +ACSVM_CodeList_BinaryOpSet(MulU) +ACSVM_CodeList_BinaryOpSet(OrIU) +ACSVM_CodeList_BinaryOpSet(OrXU) +ACSVM_CodeList_BinaryOpSet(ShLU) +ACSVM_CodeList_BinaryOpSet(ShRI) +ACSVM_CodeList_BinaryOpSet(SubU) +#undef ACSVM_CodeList_BinaryOpSet +ACSVM_CodeList(CmpI_GE, 0) +ACSVM_CodeList(CmpI_GT, 0) +ACSVM_CodeList(CmpI_LE, 0) +ACSVM_CodeList(CmpI_LT, 0) +ACSVM_CodeList(CmpU_EQ, 0) +ACSVM_CodeList(CmpU_NE, 0) +ACSVM_CodeList(DivX, 0) +ACSVM_CodeList(LAnd, 0) +ACSVM_CodeList(LOrI, 0) +ACSVM_CodeList(MulX, 0) + +// Call codes. +ACSVM_CodeList(Call_Lit, 1) +ACSVM_CodeList(Call_Stk, 0) +ACSVM_CodeList(CallFunc, 2) +ACSVM_CodeList(CallFunc_Lit, 0) +ACSVM_CodeList(CallSpec, 2) +ACSVM_CodeList(CallSpec_Lit, 0) +ACSVM_CodeList(CallSpec_R1, 2) +ACSVM_CodeList(Retn, 0) + +// Drop codes. +ACSVM_CodeList(Drop_GblArr, 1) +ACSVM_CodeList(Drop_GblReg, 1) +ACSVM_CodeList(Drop_HubArr, 1) +ACSVM_CodeList(Drop_HubReg, 1) +ACSVM_CodeList(Drop_LocArr, 1) +ACSVM_CodeList(Drop_LocReg, 1) +ACSVM_CodeList(Drop_ModArr, 1) +ACSVM_CodeList(Drop_ModReg, 1) +ACSVM_CodeList(Drop_Nul, 0) +ACSVM_CodeList(Drop_ScrRet, 0) + +// Jump codes. +ACSVM_CodeList(Jcnd_Lit, 2) +ACSVM_CodeList(Jcnd_Nil, 1) +ACSVM_CodeList(Jcnd_Tab, 1) +ACSVM_CodeList(Jcnd_Tru, 1) +ACSVM_CodeList(Jump_Lit, 1) +ACSVM_CodeList(Jump_Stk, 0) + +// Push codes. +ACSVM_CodeList(Pfun_Lit, 1) +ACSVM_CodeList(Pstr_Stk, 0) +ACSVM_CodeList(Push_GblArr, 1) +ACSVM_CodeList(Push_GblReg, 1) +ACSVM_CodeList(Push_HubArr, 1) +ACSVM_CodeList(Push_HubReg, 1) +ACSVM_CodeList(Push_Lit, 1) +ACSVM_CodeList(Push_LitArr, 0) +ACSVM_CodeList(Push_LocArr, 1) +ACSVM_CodeList(Push_LocReg, 1) +ACSVM_CodeList(Push_ModArr, 1) +ACSVM_CodeList(Push_ModReg, 1) +ACSVM_CodeList(Push_StrArs, 0) + +// Script control codes. +ACSVM_CodeList(ScrDelay, 0) +ACSVM_CodeList(ScrDelay_Lit, 1) +ACSVM_CodeList(ScrHalt, 0) +ACSVM_CodeList(ScrRestart, 0) +ACSVM_CodeList(ScrTerm, 0) +ACSVM_CodeList(ScrWaitI, 0) +ACSVM_CodeList(ScrWaitI_Lit, 1) +ACSVM_CodeList(ScrWaitS, 0) +ACSVM_CodeList(ScrWaitS_Lit, 1) + +// Stack control codes. +ACSVM_CodeList(Copy, 0) +ACSVM_CodeList(Swap, 0) + +// Unary operator codes. +#define ACSVM_CodeList_UnaryOpSet(name) \ + ACSVM_CodeList(name##_GblArr, 1) \ + ACSVM_CodeList(name##_GblReg, 1) \ + ACSVM_CodeList(name##_HubArr, 1) \ + ACSVM_CodeList(name##_HubReg, 1) \ + ACSVM_CodeList(name##_LocArr, 1) \ + ACSVM_CodeList(name##_LocReg, 1) \ + ACSVM_CodeList(name##_ModArr, 1) \ + ACSVM_CodeList(name##_ModReg, 1) +ACSVM_CodeList_UnaryOpSet(DecU) +ACSVM_CodeList_UnaryOpSet(IncU) +#undef ACSVM_CodeList_UnaryOpSet +ACSVM_CodeList(InvU, 0) +ACSVM_CodeList(NegI, 0) +ACSVM_CodeList(NotU, 0) + +#undef ACSVM_CodeList +#endif + + +#ifdef ACSVM_CodeListACS0 + +ACSVM_CodeListACS0(Nop, 0, "", Nop, 0, None) +ACSVM_CodeListACS0(ScrTerm, 1, "", ScrTerm, 0, None) +ACSVM_CodeListACS0(ScrHalt, 2, "", ScrHalt, 0, None) +ACSVM_CodeListACS0(Push_Lit, 3, "W", Push_Lit, 0, None) +ACSVM_CodeListACS0(CallSpec_1, 4, "b", CallSpec, 1, None) +ACSVM_CodeListACS0(CallSpec_2, 5, "b", CallSpec, 2, None) +ACSVM_CodeListACS0(CallSpec_3, 6, "b", CallSpec, 3, None) +ACSVM_CodeListACS0(CallSpec_4, 7, "b", CallSpec, 4, None) +ACSVM_CodeListACS0(CallSpec_5, 8, "b", CallSpec, 5, None) +ACSVM_CodeListACS0(CallSpec_1L, 9, "bW", CallSpec_Lit, 0, None) +ACSVM_CodeListACS0(CallSpec_2L, 10, "bWW", CallSpec_Lit, 0, None) +ACSVM_CodeListACS0(CallSpec_3L, 11, "bWWW", CallSpec_Lit, 0, None) +ACSVM_CodeListACS0(CallSpec_4L, 12, "bWWWW", CallSpec_Lit, 0, None) +ACSVM_CodeListACS0(CallSpec_5L, 13, "bWWWWW", CallSpec_Lit, 0, None) +ACSVM_CodeListACS0(AddU, 14, "", AddU, 2, None) +ACSVM_CodeListACS0(SubU, 15, "", SubU, 2, None) +ACSVM_CodeListACS0(MulU, 16, "", MulU, 2, None) +ACSVM_CodeListACS0(DivI, 17, "", DivI, 2, None) +ACSVM_CodeListACS0(ModI, 18, "", ModI, 2, None) +ACSVM_CodeListACS0(CmpU_EQ, 19, "", CmpU_EQ, 2, None) +ACSVM_CodeListACS0(CmpU_NE, 20, "", CmpU_NE, 2, None) +ACSVM_CodeListACS0(CmpI_LT, 21, "", CmpI_LT, 2, None) +ACSVM_CodeListACS0(CmpI_GT, 22, "", CmpI_GT, 2, None) +ACSVM_CodeListACS0(CmpI_LE, 23, "", CmpI_LE, 2, None) +ACSVM_CodeListACS0(CmpI_GE, 24, "", CmpI_GE, 2, None) +ACSVM_CodeListACS0(Drop_LocReg, 25, "bL", Drop_LocReg, 1, None) +ACSVM_CodeListACS0(Drop_ModReg, 26, "bO", Drop_ModReg, 1, None) +ACSVM_CodeListACS0(Drop_HubReg, 27, "bU", Drop_HubReg, 1, None) +ACSVM_CodeListACS0(Push_LocReg, 28, "bL", Push_LocReg, 1, None) +ACSVM_CodeListACS0(Push_ModReg, 29, "bO", Push_ModReg, 1, None) +ACSVM_CodeListACS0(Push_HubReg, 30, "bU", Push_HubReg, 1, None) +ACSVM_CodeListACS0(AddU_LocReg, 31, "bL", AddU_LocReg, 1, None) +ACSVM_CodeListACS0(AddU_ModReg, 32, "bO", AddU_ModReg, 1, None) +ACSVM_CodeListACS0(AddU_HubReg, 33, "bU", AddU_HubReg, 1, None) +ACSVM_CodeListACS0(SubU_LocReg, 34, "bL", SubU_LocReg, 1, None) +ACSVM_CodeListACS0(SubU_ModReg, 35, "bO", SubU_ModReg, 1, None) +ACSVM_CodeListACS0(SubU_HubReg, 36, "bU", SubU_HubReg, 1, None) +ACSVM_CodeListACS0(MulU_LocReg, 37, "bL", MulU_LocReg, 1, None) +ACSVM_CodeListACS0(MulU_ModReg, 38, "bO", MulU_ModReg, 1, None) +ACSVM_CodeListACS0(MulU_HubReg, 39, "bU", MulU_HubReg, 1, None) +ACSVM_CodeListACS0(DivI_LocReg, 40, "bL", DivI_LocReg, 1, None) +ACSVM_CodeListACS0(DivI_ModReg, 41, "bO", DivI_ModReg, 1, None) +ACSVM_CodeListACS0(DivI_HubReg, 42, "bU", DivI_HubReg, 1, None) +ACSVM_CodeListACS0(ModI_LocReg, 43, "bL", ModI_LocReg, 1, None) +ACSVM_CodeListACS0(ModI_ModReg, 44, "bO", ModI_ModReg, 1, None) +ACSVM_CodeListACS0(ModI_HubReg, 45, "bU", ModI_HubReg, 1, None) +ACSVM_CodeListACS0(IncU_LocReg, 46, "bL", IncU_LocReg, 1, None) +ACSVM_CodeListACS0(IncU_ModReg, 47, "bO", IncU_ModReg, 1, None) +ACSVM_CodeListACS0(IncU_HubReg, 48, "bU", IncU_HubReg, 1, None) +ACSVM_CodeListACS0(DecU_LocReg, 49, "bL", DecU_LocReg, 1, None) +ACSVM_CodeListACS0(DecU_ModReg, 50, "bO", DecU_ModReg, 1, None) +ACSVM_CodeListACS0(DecU_HubReg, 51, "bU", DecU_HubReg, 1, None) +ACSVM_CodeListACS0(Jump_Lit, 52, "WJ", Jump_Lit, 0, None) +ACSVM_CodeListACS0(Jcnd_Tru, 53, "WJ", Jcnd_Tru, 1, None) +ACSVM_CodeListACS0(Drop_Nul, 54, "", Drop_Nul, 1, None) +ACSVM_CodeListACS0(ScrDelay, 55, "", ScrDelay, 1, None) +ACSVM_CodeListACS0(ScrDelay_Lit, 56, "W", ScrDelay_Lit, 0, None) + +ACSVM_CodeListACS0(ScrRestart, 69, "", ScrRestart, 0, None) +ACSVM_CodeListACS0(LAnd, 70, "", LAnd, 2, None) +ACSVM_CodeListACS0(LOrI, 71, "", LOrI, 2, None) +ACSVM_CodeListACS0(AndU, 72, "", AndU, 2, None) +ACSVM_CodeListACS0(OrIU, 73, "", OrIU, 2, None) +ACSVM_CodeListACS0(OrXU, 74, "", OrXU, 2, None) +ACSVM_CodeListACS0(NotU, 75, "", NotU, 1, None) +ACSVM_CodeListACS0(ShLU, 76, "", ShLU, 2, None) +ACSVM_CodeListACS0(ShRI, 77, "", ShRI, 2, None) +ACSVM_CodeListACS0(NegI, 78, "", NegI, 1, None) +ACSVM_CodeListACS0(Jcnd_Nil, 79, "WJ", Jcnd_Nil, 1, None) + +ACSVM_CodeListACS0(ScrWaitI, 81, "", ScrWaitI, 1, None) +ACSVM_CodeListACS0(ScrWaitI_Lit, 82, "W", ScrWaitI_Lit, 0, None) + +ACSVM_CodeListACS0(Jcnd_Lit, 84, "WWJ", Jcnd_Lit, 1, None) +ACSVM_CodeListACS0(PrintPush, 85, "", CallFunc, 0, PrintPush) + +ACSVM_CodeListACS0(PrintString, 87, "", CallFunc, 1, PrintString) +ACSVM_CodeListACS0(PrintIntD, 88, "", CallFunc, 1, PrintIntD) +ACSVM_CodeListACS0(PrintChar, 89, "", CallFunc, 1, PrintChar) + +ACSVM_CodeListACS0(MulX, 136, "", MulX, 2, None) +ACSVM_CodeListACS0(DivX, 137, "", DivX, 2, None) + +ACSVM_CodeListACS0(PrintFixD, 157, "", CallFunc, 1, PrintFixD) + +ACSVM_CodeListACS0(Push_LitB, 167, "B", Push_Lit, 0, None) +ACSVM_CodeListACS0(CallSpec_1LB, 168, "BB", CallSpec_Lit, 0, None) +ACSVM_CodeListACS0(CallSpec_2LB, 169, "BBB", CallSpec_Lit, 0, None) +ACSVM_CodeListACS0(CallSpec_3LB, 170, "BBBB", CallSpec_Lit, 0, None) +ACSVM_CodeListACS0(CallSpec_4LB, 171, "BBBBB", CallSpec_Lit, 0, None) +ACSVM_CodeListACS0(CallSpec_5LB, 172, "BBBBBB", CallSpec_Lit, 0, None) +ACSVM_CodeListACS0(ScrDelay_LB, 173, "B", ScrDelay_Lit, 0, None) +ACSVM_CodeListACS0(Push_LitArrB, 175, "", Push_LitArr, 0, None) +ACSVM_CodeListACS0(Push_Lit2B, 176, "BB", Push_LitArr, 0, None) +ACSVM_CodeListACS0(Push_Lit3B, 177, "BBB", Push_LitArr, 0, None) +ACSVM_CodeListACS0(Push_Lit4B, 178, "BBBB", Push_LitArr, 0, None) +ACSVM_CodeListACS0(Push_Lit5B, 179, "BBBBB", Push_LitArr, 0, None) + +ACSVM_CodeListACS0(Drop_GblReg, 181, "bG", Drop_GblReg, 1, None) +ACSVM_CodeListACS0(Push_GblReg, 182, "bG", Push_GblReg, 0, None) +ACSVM_CodeListACS0(AddU_GblReg, 183, "bG", AddU_GblReg, 1, None) +ACSVM_CodeListACS0(SubU_GblReg, 184, "bG", SubU_GblReg, 1, None) +ACSVM_CodeListACS0(MulU_GblReg, 185, "bG", MulU_GblReg, 1, None) +ACSVM_CodeListACS0(DivI_GblReg, 186, "bG", DivI_GblReg, 1, None) +ACSVM_CodeListACS0(ModI_GblReg, 187, "bG", ModI_GblReg, 1, None) +ACSVM_CodeListACS0(IncU_GblReg, 188, "bG", IncU_GblReg, 1, None) +ACSVM_CodeListACS0(DecU_GblReg, 189, "bG", DecU_GblReg, 1, None) + +ACSVM_CodeListACS0(Call_Lit, 203, "b", Call_Lit, 0, None) +ACSVM_CodeListACS0(Call_Nul, 204, "b", Call_Lit, 0, None) +ACSVM_CodeListACS0(Retn_Nul, 205, "", Retn, 0, None) +ACSVM_CodeListACS0(Retn_Stk, 206, "", Retn, 0, None) +ACSVM_CodeListACS0(Push_ModArr, 207, "bo", Push_ModArr, 1, None) +ACSVM_CodeListACS0(Drop_ModArr, 208, "bo", Drop_ModArr, 2, None) +ACSVM_CodeListACS0(AddU_ModArr, 209, "bo", AddU_ModArr, 2, None) +ACSVM_CodeListACS0(SubU_ModArr, 210, "bo", SubU_ModArr, 2, None) +ACSVM_CodeListACS0(MulU_ModArr, 211, "bo", MulU_ModArr, 2, None) +ACSVM_CodeListACS0(DivI_ModArr, 212, "bo", DivI_ModArr, 2, None) +ACSVM_CodeListACS0(ModI_ModArr, 213, "bo", ModI_ModArr, 2, None) +ACSVM_CodeListACS0(IncU_ModArr, 214, "bo", IncU_ModArr, 2, None) +ACSVM_CodeListACS0(DecU_ModArr, 215, "bo", DecU_ModArr, 2, None) +ACSVM_CodeListACS0(Copy, 216, "", Copy, 1, None) +ACSVM_CodeListACS0(Swap, 217, "", Swap, 2, None) + +ACSVM_CodeListACS0(Pstr_Stk, 225, "", Pstr_Stk, 1, None) +ACSVM_CodeListACS0(Push_HubArr, 226, "bu", Push_HubArr, 1, None) +ACSVM_CodeListACS0(Drop_HubArr, 227, "bu", Drop_HubArr, 2, None) +ACSVM_CodeListACS0(AddU_HubArr, 228, "bu", AddU_HubArr, 2, None) +ACSVM_CodeListACS0(SubU_HubArr, 229, "bu", SubU_HubArr, 2, None) +ACSVM_CodeListACS0(MulU_HubArr, 230, "bu", MulU_HubArr, 2, None) +ACSVM_CodeListACS0(DivI_HubArr, 231, "bu", DivI_HubArr, 2, None) +ACSVM_CodeListACS0(ModI_HubArr, 232, "bu", ModI_HubArr, 2, None) +ACSVM_CodeListACS0(IncU_HubArr, 233, "bu", IncU_HubArr, 2, None) +ACSVM_CodeListACS0(DecU_HubArr, 234, "bu", DecU_HubArr, 2, None) +ACSVM_CodeListACS0(Push_GblArr, 235, "bg", Push_GblArr, 1, None) +ACSVM_CodeListACS0(Drop_GblArr, 236, "bg", Drop_GblArr, 2, None) +ACSVM_CodeListACS0(AddU_GblArr, 237, "bg", AddU_GblArr, 2, None) +ACSVM_CodeListACS0(SubU_GblArr, 238, "bg", SubU_GblArr, 2, None) +ACSVM_CodeListACS0(MulU_GblArr, 239, "bg", MulU_GblArr, 2, None) +ACSVM_CodeListACS0(DivI_GblArr, 240, "bg", DivI_GblArr, 2, None) +ACSVM_CodeListACS0(ModI_GblArr, 241, "bg", ModI_GblArr, 2, None) +ACSVM_CodeListACS0(IncU_GblArr, 242, "bg", IncU_GblArr, 2, None) +ACSVM_CodeListACS0(DecU_GblArr, 243, "bg", DecU_GblArr, 2, None) + +ACSVM_CodeListACS0(StrLen, 253, "", CallFunc, 1, StrLen) + +ACSVM_CodeListACS0(Jcnd_Tab, 256, "", Jcnd_Tab, 1, None) +ACSVM_CodeListACS0(Drop_ScrRet, 257, "", Drop_ScrRet, 1, None) + +ACSVM_CodeListACS0(CallSpec_5R1, 263, "b", CallSpec_R1, 5, None) + +ACSVM_CodeListACS0(PrintModArr, 273, "", CallFunc, 2, PrintModArr) +ACSVM_CodeListACS0(PrintHubArr, 274, "", CallFunc, 2, PrintHubArr) +ACSVM_CodeListACS0(PrintGblArr, 275, "", CallFunc, 2, PrintGblArr) + +ACSVM_CodeListACS0(AndU_LocReg, 291, "bL", AndU_LocReg, 1, None) +ACSVM_CodeListACS0(AndU_ModReg, 292, "bO", AndU_ModReg, 1, None) +ACSVM_CodeListACS0(AndU_HubReg, 293, "bU", AndU_HubReg, 1, None) +ACSVM_CodeListACS0(AndU_GblReg, 294, "bG", AndU_GblReg, 1, None) +ACSVM_CodeListACS0(AndU_ModArr, 295, "bo", AndU_ModArr, 2, None) +ACSVM_CodeListACS0(AndU_HubArr, 296, "bu", AndU_HubArr, 2, None) +ACSVM_CodeListACS0(AndU_GblArr, 297, "bg", AndU_GblArr, 2, None) +ACSVM_CodeListACS0(OrXU_LocReg, 298, "bL", OrXU_LocReg, 1, None) +ACSVM_CodeListACS0(OrXU_ModReg, 299, "bO", OrXU_ModReg, 1, None) +ACSVM_CodeListACS0(OrXU_HubReg, 300, "bU", OrXU_HubReg, 1, None) +ACSVM_CodeListACS0(OrXU_GblReg, 301, "bG", OrXU_GblReg, 1, None) +ACSVM_CodeListACS0(OrXU_ModArr, 302, "bo", OrXU_ModArr, 2, None) +ACSVM_CodeListACS0(OrXU_HubArr, 303, "bu", OrXU_HubArr, 2, None) +ACSVM_CodeListACS0(OrXU_GblArr, 304, "bg", OrXU_GblArr, 2, None) +ACSVM_CodeListACS0(OrIU_LocReg, 305, "bL", OrIU_LocReg, 1, None) +ACSVM_CodeListACS0(OrIU_ModReg, 306, "bO", OrIU_ModReg, 1, None) +ACSVM_CodeListACS0(OrIU_HubReg, 307, "bU", OrIU_HubReg, 1, None) +ACSVM_CodeListACS0(OrIU_GblReg, 308, "bG", OrIU_GblReg, 1, None) +ACSVM_CodeListACS0(OrIU_ModArr, 309, "bo", OrIU_ModArr, 2, None) +ACSVM_CodeListACS0(OrIU_HubArr, 310, "bu", OrIU_HubArr, 2, None) +ACSVM_CodeListACS0(OrIU_GblArr, 311, "bg", OrIU_GblArr, 2, None) +ACSVM_CodeListACS0(ShLU_LocReg, 312, "bL", ShLU_LocReg, 1, None) +ACSVM_CodeListACS0(ShLU_ModReg, 313, "bO", ShLU_ModReg, 1, None) +ACSVM_CodeListACS0(ShLU_HubReg, 314, "bU", ShLU_HubReg, 1, None) +ACSVM_CodeListACS0(ShLU_GblReg, 315, "bG", ShLU_GblReg, 1, None) +ACSVM_CodeListACS0(ShLU_ModArr, 316, "bo", ShLU_ModArr, 2, None) +ACSVM_CodeListACS0(ShLU_HubArr, 317, "bu", ShLU_HubArr, 2, None) +ACSVM_CodeListACS0(ShLU_GblArr, 318, "bg", ShLU_GblArr, 2, None) +ACSVM_CodeListACS0(ShRI_LocReg, 319, "bL", ShRI_LocReg, 1, None) +ACSVM_CodeListACS0(ShRI_ModReg, 320, "bO", ShRI_ModReg, 1, None) +ACSVM_CodeListACS0(ShRI_HubReg, 321, "bU", ShRI_HubReg, 1, None) +ACSVM_CodeListACS0(ShRI_GblReg, 322, "bG", ShRI_GblReg, 1, None) +ACSVM_CodeListACS0(ShRI_ModArr, 323, "bo", ShRI_ModArr, 2, None) +ACSVM_CodeListACS0(ShRI_HubArr, 324, "bu", ShRI_HubArr, 2, None) +ACSVM_CodeListACS0(ShRI_GblArr, 325, "bg", ShRI_GblArr, 2, None) + +ACSVM_CodeListACS0(InvU, 330, "", InvU, 1, None) + +ACSVM_CodeListACS0(PrintIntB, 349, "", CallFunc, 1, PrintIntB) +ACSVM_CodeListACS0(PrintIntX, 350, "", CallFunc, 1, PrintIntX) +ACSVM_CodeListACS0(CallFunc, 351, "bh", CallFunc, 0, None) +ACSVM_CodeListACS0(PrintEndStr, 352, "", CallFunc, 0, PrintEndStr) +ACSVM_CodeListACS0(PrintModArrR, 353, "", CallFunc, 4, PrintModArr) +ACSVM_CodeListACS0(PrintHubArrR, 354, "", CallFunc, 4, PrintHubArr) +ACSVM_CodeListACS0(PrintGblArrR, 355, "", CallFunc, 4, PrintGblArr) +ACSVM_CodeListACS0(StrCpyModArr, 356, "", CallFunc, 6, StrCpyModArr) +ACSVM_CodeListACS0(StrCpyHubArr, 357, "", CallFunc, 6, StrCpyHubArr) +ACSVM_CodeListACS0(StrCpyGblArr, 358, "", CallFunc, 6, StrCpyGblArr) +ACSVM_CodeListACS0(Pfun_Lit, 359, "b", Pfun_Lit, 0, None) +ACSVM_CodeListACS0(Call_Stk, 360, "", Call_Stk, 1, None) +ACSVM_CodeListACS0(ScrWaitS, 361, "", ScrWaitS, 1, None) + +ACSVM_CodeListACS0(Jump_Stk, 363, "", Jump_Stk, 1, None) +ACSVM_CodeListACS0(Drop_LocArr, 364, "bl", Drop_LocArr, 2, None) +ACSVM_CodeListACS0(Push_LocArr, 365, "bl", Push_LocArr, 1, None) +ACSVM_CodeListACS0(AddU_LocArr, 366, "bl", AddU_LocArr, 2, None) +ACSVM_CodeListACS0(SubU_LocArr, 367, "bl", SubU_LocArr, 2, None) +ACSVM_CodeListACS0(MulU_LocArr, 368, "bl", MulU_LocArr, 2, None) +ACSVM_CodeListACS0(DivI_LocArr, 369, "bl", DivI_LocArr, 2, None) +ACSVM_CodeListACS0(ModI_LocArr, 370, "bl", ModI_LocArr, 2, None) +ACSVM_CodeListACS0(IncU_LocArr, 371, "bl", IncU_LocArr, 2, None) +ACSVM_CodeListACS0(DecU_LocArr, 372, "bl", DecU_LocArr, 2, None) +ACSVM_CodeListACS0(AndU_LocArr, 373, "bl", AndU_LocArr, 2, None) +ACSVM_CodeListACS0(OrXU_LocArr, 374, "bl", OrXU_LocArr, 2, None) +ACSVM_CodeListACS0(OrIU_LocArr, 375, "bl", OrIU_LocArr, 2, None) +ACSVM_CodeListACS0(ShLU_LocArr, 376, "bl", ShLU_LocArr, 2, None) +ACSVM_CodeListACS0(ShRI_LocArr, 377, "bl", ShRI_LocArr, 2, None) +ACSVM_CodeListACS0(PrintLocArr, 378, "", CallFunc, 2, PrintLocArr) +ACSVM_CodeListACS0(PrintLocArrR, 379, "", CallFunc, 4, PrintLocArr) +ACSVM_CodeListACS0(StrCpyLocArr, 380, "", CallFunc, 6, StrCpyLocArr) + +ACSVM_CodeListACS0(CallSpec_5Ex, 381, "W", CallSpec, 5, None) +ACSVM_CodeListACS0(CallSpec_6, 500, "b", CallSpec, 6, None) +ACSVM_CodeListACS0(CallSpec_7, 501, "b", CallSpec, 7, None) +ACSVM_CodeListACS0(CallSpec_8, 502, "b", CallSpec, 8, None) +ACSVM_CodeListACS0(CallSpec_9, 503, "b", CallSpec, 9, None) +ACSVM_CodeListACS0(CallSpec_10, 504, "b", CallSpec, 10, None) +ACSVM_CodeListACS0(CallSpec_6L, 505, "bWWWWWW", CallSpec_Lit, 0, None) +ACSVM_CodeListACS0(CallSpec_7L, 506, "bWWWWWWWW", CallSpec_Lit, 0, None) +ACSVM_CodeListACS0(CallSpec_8L, 507, "bWWWWWWWWW", CallSpec_Lit, 0, None) +ACSVM_CodeListACS0(CallSpec_9L, 508, "bWWWWWWWWWW", CallSpec_Lit, 0, None) +ACSVM_CodeListACS0(CallSpec_10L, 509, "bWWWWWWWWWWW", CallSpec_Lit, 0, None) +ACSVM_CodeListACS0(CallSpec_6LB, 510, "BBBBBBB", CallSpec_Lit, 0, None) +ACSVM_CodeListACS0(CallSpec_7LB, 511, "BBBBBBBB", CallSpec_Lit, 0, None) +ACSVM_CodeListACS0(CallSpec_8LB, 512, "BBBBBBBBB", CallSpec_Lit, 0, None) +ACSVM_CodeListACS0(CallSpec_9LB, 513, "BBBBBBBBBB", CallSpec_Lit, 0, None) +ACSVM_CodeListACS0(CallSpec_10LB,514, "BBBBBBBBBBB", CallSpec_Lit, 0, None) +ACSVM_CodeListACS0(Push_Lit6B, 515, "BBBBBB", Push_LitArr, 0, None) +ACSVM_CodeListACS0(Push_Lit7B, 516, "BBBBBBB", Push_LitArr, 0, None) +ACSVM_CodeListACS0(Push_Lit8B, 517, "BBBBBBBB", Push_LitArr, 0, None) +ACSVM_CodeListACS0(Push_Lit9B, 518, "BBBBBBBBB", Push_LitArr, 0, None) +ACSVM_CodeListACS0(Push_Lit10B, 519, "BBBBBBBBBB", Push_LitArr, 0, None) +ACSVM_CodeListACS0(CallSpec_10R1,520, "b", CallSpec_R1, 10, None) + +#undef ACSVM_CodeListACS0 +#endif + + +#ifdef ACSVM_FuncList + +ACSVM_FuncList(Nop) +ACSVM_FuncList(Kill) + +// Printing functions. +ACSVM_FuncList(PrintChar) +ACSVM_FuncList(PrintEndStr) +ACSVM_FuncList(PrintFixD) +ACSVM_FuncList(PrintGblArr) +ACSVM_FuncList(PrintHubArr) +ACSVM_FuncList(PrintIntB) +ACSVM_FuncList(PrintIntD) +ACSVM_FuncList(PrintIntX) +ACSVM_FuncList(PrintLocArr) +ACSVM_FuncList(PrintModArr) +ACSVM_FuncList(PrintPush) +ACSVM_FuncList(PrintString) + +// Script functions. +ACSVM_FuncList(ScrPauseS) +ACSVM_FuncList(ScrStartS) +ACSVM_FuncList(ScrStartSD) // Locked Door +ACSVM_FuncList(ScrStartSF) // Forced +ACSVM_FuncList(ScrStartSL) // Locked +ACSVM_FuncList(ScrStartSR) // Result +ACSVM_FuncList(ScrStopS) + +// String functions. +ACSVM_FuncList(GetChar) +ACSVM_FuncList(StrCaseCmp) +ACSVM_FuncList(StrCmp) +ACSVM_FuncList(StrCpyGblArr) +ACSVM_FuncList(StrCpyHubArr) +ACSVM_FuncList(StrCpyLocArr) +ACSVM_FuncList(StrCpyModArr) +ACSVM_FuncList(StrLeft) +ACSVM_FuncList(StrLen) +ACSVM_FuncList(StrMid) +ACSVM_FuncList(StrRight) + +#undef ACSVM_FuncList +#endif + + +#ifdef ACSVM_FuncListACS0 + +ACSVM_FuncListACS0(GetChar, 15, GetChar, {{2, Code::Push_StrArs}}) + +ACSVM_FuncListACS0(ScrStartS, 39, ScrStartS, {}) +ACSVM_FuncListACS0(ScrPauseS, 40, ScrPauseS, {}) +ACSVM_FuncListACS0(ScrStopS, 41, ScrStopS, {}) +ACSVM_FuncListACS0(ScrStartSL, 42, ScrStartSL, {}) +ACSVM_FuncListACS0(ScrStartSD, 43, ScrStartSD, {}) +ACSVM_FuncListACS0(ScrStartSR, 44, ScrStartSR, {}) +ACSVM_FuncListACS0(ScrStartSF, 45, ScrStartSF, {}) + +ACSVM_FuncListACS0(StrCmp, 63, StrCmp, {}) +ACSVM_FuncListACS0(StrCaseCmp, 64, StrCaseCmp, {}) +ACSVM_FuncListACS0(StrLeft, 65, StrLeft, {}) +ACSVM_FuncListACS0(StrRight, 66, StrRight, {}) +ACSVM_FuncListACS0(StrMid, 67, StrMid, {}) + +#undef ACSVM_FuncListACS0 +#endif + +// EOF + diff --git a/src/acs/vm/ACSVM/Environment.cpp b/src/acs/vm/ACSVM/Environment.cpp new file mode 100644 index 0000000000000000000000000000000000000000..6461ec5140059e3dab85f2f75af673e89634a2a2 --- /dev/null +++ b/src/acs/vm/ACSVM/Environment.cpp @@ -0,0 +1,908 @@ +//----------------------------------------------------------------------------- +// +// Copyright (C) 2015-2017 David Hill +// +// See COPYING for license information. +// +//----------------------------------------------------------------------------- +// +// Environment class. +// +//----------------------------------------------------------------------------- + +#include "Environment.hpp" + +#include "Action.hpp" +#include "BinaryIO.hpp" +#include "CallFunc.hpp" +#include "Code.hpp" +#include "CodeData.hpp" +#include "Function.hpp" +#include "HashMap.hpp" +#include "Module.hpp" +#include "PrintBuf.hpp" +#include "Scope.hpp" +#include "Script.hpp" +#include "Serial.hpp" +#include "Thread.hpp" + +#include <iostream> +#include <list> +#include <unordered_map> +#include <vector> + + +//----------------------------------------------------------------------------| +// Types | +// + +namespace ACSVM +{ + // + // Environment::PrivData + // + struct Environment::PrivData + { + using FuncName = std::pair<ModuleName, String *>; + using FuncElem = HashMapElem<FuncName, Word>; + + struct NameEqual + { + bool operator () (ModuleName const *l, ModuleName const *r) const + {return *l == *r;} + }; + + struct NameHash + { + std::size_t operator () (ModuleName const *name) const + {return name->hash();} + }; + + struct FuncNameHash + { + std::size_t operator () (FuncName const &name) const + {return name.first.hash() + name.second->hash;} + }; + + + // Reserve index 0 as no function. + std::vector<Function *> functionByIdx{nullptr}; + + HashMapKeyExt<FuncName, Word, FuncNameHash> functionByName{16, 16}; + + HashMapKeyMem<ModuleName, Module, &Module::name, &Module::hashLink> modules; + + HashMapKeyMem<Word, GlobalScope, &GlobalScope::id, &GlobalScope::hashLink> scopes; + + std::vector<CallFunc> tableCallFunc + { + #define ACSVM_FuncList(name) \ + CallFunc_Func_##name, + #include "CodeList.hpp" + + CallFunc_Func_Nop + }; + + std::unordered_map<Word, CodeDataACS0> tableCodeDataACS0 + { + #define ACSVM_CodeListACS0(name, code, args, transCode, stackArgC, transFunc) \ + {code, {CodeACS0::name, args, Code::transCode, stackArgC, Func::transFunc}}, + #include "CodeList.hpp" + }; + + std::unordered_map<Word, FuncDataACS0> tableFuncDataACS0 + { + #define ACSVM_FuncListACS0(name, func, transFunc, ...) \ + {func, {FuncACS0::name, Func::transFunc, __VA_ARGS__}}, + #include "CodeList.hpp" + }; + }; +} + + +//----------------------------------------------------------------------------| +// Extern Functions | +// + +namespace ACSVM +{ + // + // Environment constructor + // + Environment::Environment() : + branchLimit {0}, + scriptLocRegC{ScriptLocRegCDefault}, + + funcV{nullptr}, + funcC{0}, + + pd{new PrivData} + { + funcV = pd->functionByIdx.data(); + funcC = pd->functionByIdx.size(); + } + + // + // Environment destructor + // + Environment::~Environment() + { + pd->functionByName.free(); + pd->modules.free(); + pd->scopes.free(); + + while(scriptAction.next->obj) + delete scriptAction.next->obj; + + delete pd; + + // Deallocate threads. Do this after scopes have been destructed. + while(threadFree.next->obj) + delete threadFree.next->obj; + } + + // + // Environment::addCallFunc + // + Word Environment::addCallFunc(CallFunc func) + { + pd->tableCallFunc.push_back(func); + return pd->tableCallFunc.size() - 1; + } + + // + // Environment::addCodeDataACS0 + // + void Environment::addCodeDataACS0(Word code, CodeDataACS0 &&data) + { + auto itr = pd->tableCodeDataACS0.find(code); + if(itr == pd->tableCodeDataACS0.end()) + pd->tableCodeDataACS0.emplace(code, std::move(data)); + else + itr->second = std::move(data); + } + + // + // Environment::addFuncDataACS0 + // + void Environment::addFuncDataACS0(Word func, FuncDataACS0 &&data) + { + auto itr = pd->tableFuncDataACS0.find(func); + if(itr == pd->tableFuncDataACS0.end()) + pd->tableFuncDataACS0.emplace(func, std::move(data)); + else + itr->second = std::move(data); + } + + // + // Environment::allocThread + // + Thread *Environment::allocThread() + { + return new Thread(this); + } + + // + // Environment::callFunc + // + bool Environment::callFunc(Thread *thread, Word func, Word const *argV, Word argC) + { + return pd->tableCallFunc[func](thread, argV, argC); + } + + // + // Environment::callSpec + // + Word Environment::callSpec(Thread *thread, Word spec, Word const *argV, Word argC) + { + if(thread->scopeMap->clampCallSpec && thread->module->isACS0) + { + Vector<Word> argTmp{argV, argC}; + for(auto &arg : argTmp) arg &= 0xFF; + return callSpecImpl(thread, spec, argTmp.data(), argTmp.size()); + } + else + return callSpecImpl(thread, spec, argV, argC); + } + + // + // Environment::callSpecImpl + // + Word Environment::callSpecImpl(Thread *, Word, Word const *, Word) + { + return 0; + } + + // + // Environment::checkLock + // + bool Environment::checkLock(Thread *, Word, bool) + { + return false; + } + + // + // Environment::checkTag + // + bool Environment::checkTag(Word, Word) + { + return false; + } + + // + // Environment::collectStrings + // + void Environment::collectStrings() + { + stringTable.collectBegin(); + refStrings(); + stringTable.collectEnd(); + } + + // + // Environment::countActiveThread + // + std::size_t Environment::countActiveThread() const + { + std::size_t n = 0; + + for(auto &scope : pd->scopes) + { + if(scope.active) + n += scope.countActiveThread(); + } + + return n; + } + + // + // Environment::deferAction + // + void Environment::deferAction(ScriptAction &&action) + { + (new ScriptAction(std::move(action)))->link.insert(&scriptAction); + } + + // + // Environment::exec + // + void Environment::exec() + { + // Delegate deferred script actions. + for(auto itr = scriptAction.begin(), end = scriptAction.end(); itr != end;) + { + auto scope = pd->scopes.find(itr->id.global); + if(scope && scope->active) + itr++->link.relink(&scope->scriptAction); + else + ++itr; + } + + for(auto &scope : pd->scopes) + { + if(scope.active) + scope.exec(); + } + } + + // + // Environment::findCodeDataACS0 + // + CodeDataACS0 const *Environment::findCodeDataACS0(Word code) + { + auto itr = pd->tableCodeDataACS0.find(code); + return itr == pd->tableCodeDataACS0.end() ? nullptr : &itr->second; + } + + // + // Environment::findFuncDataACS0 + // + FuncDataACS0 const *Environment::findFuncDataACS0(Word func) + { + auto itr = pd->tableFuncDataACS0.find(func); + return itr == pd->tableFuncDataACS0.end() ? nullptr : &itr->second; + } + + // + // Environment::findModule + // + Module *Environment::findModule(ModuleName const &name) const + { + return pd->modules.find(name); + } + + // + // Environment::freeFunction + // + void Environment::freeFunction(Function *func) + { + // Null every reference to this function in every Module. + // O(N*M) is not very nice, but that can be fixed if/when it comes up. + for(auto &module : pd->modules) + { + for(Function *&funcItr : module.functionV) + { + if(funcItr == func) + funcItr = nullptr; + } + } + + pd->functionByIdx[func->idx] = nullptr; + delete func; + } + + // + // Environment::freeGlobalScope + // + void Environment::freeGlobalScope(GlobalScope *scope) + { + pd->scopes.unlink(scope); + delete scope; + } + + // + // Environment::freeModule + // + void Environment::freeModule(Module *module) + { + pd->modules.unlink(module); + delete module; + } + + // + // Environment::freeThread + // + void Environment::freeThread(Thread *thread) + { + thread->link.relink(&threadFree); + } + + // + // Environment::getCodeData + // + CodeData const *Environment::getCodeData(Code code) + { + switch(code) + { + #define ACSVM_CodeList(name, argc) case Code::name: \ + {static CodeData const data{Code::name, argc}; return &data;} + #include "CodeList.hpp" + + default: + case Code::None: + static CodeData const dataNone{Code::None, 0}; + return &dataNone; + } + } + + // + // Environment::getFreeThread + // + Thread *Environment::getFreeThread() + { + if(threadFree.next->obj) + { + Thread *thread = threadFree.next->obj; + thread->link.unlink(); + return thread; + } + else + return allocThread(); + } + + // + // Environment::getFunction + // + Function *Environment::getFunction(Module *module, String *funcName) + { + if(funcName) + { + PrivData::FuncName namePair{module->name, funcName}; + auto idx = pd->functionByName.find(namePair); + + if(!idx) + { + #if SIZE_MAX > UINT32_MAX + if(pd->functionByIdx.size() > UINT32_MAX) + throw std::bad_alloc(); + #endif + + idx = new PrivData::FuncElem{std::move(namePair), + static_cast<Word>(pd->functionByIdx.size())}; + pd->functionByName.insert(idx); + + pd->functionByIdx.emplace_back(); + funcV = pd->functionByIdx.data(); + funcC = pd->functionByIdx.size(); + } + + auto &ptr = pd->functionByIdx[idx->val]; + + if(!ptr) + ptr = new Function{module, funcName, idx->val}; + + return ptr; + } + else + return new Function{module, nullptr, 0}; + } + + // + // Environment::getGlobalScope + // + GlobalScope *Environment::getGlobalScope(Word id) + { + if(auto *scope = pd->scopes.find(id)) + return scope; + + auto scope = new GlobalScope(this, id); + pd->scopes.insert(scope); + return scope; + } + + // + // Environment::getModule + // + Module *Environment::getModule(ModuleName const &name) + { + auto module = pd->modules.find(name); + + if(!module) + { + module = new Module{this, name}; + pd->modules.insert(module); + loadModule(module); + } + else + { + if(!module->loaded) + loadModule(module); + } + + return module; + } + + // + // Environment::getModuleName + // + ModuleName Environment::getModuleName(char const *str) + { + return getModuleName(str, std::strlen(str)); + } + + // + // Environment::getModuleName + // + ModuleName Environment::getModuleName(char const *str, std::size_t len) + { + return {getString(str, len), nullptr, 0}; + } + + // + // Environment::hasActiveThread + // + bool Environment::hasActiveThread() const + { + for(auto &scope : pd->scopes) + { + if(scope.active && scope.hasActiveThread()) + return true; + } + + return false; + } + + // + // Environment::loadFunctions + // + void Environment::loadFunctions(Serial &in) + { + // Function index map. + pd->functionByName.free(); + for(std::size_t n = ReadVLN<std::size_t>(in); n--;) + { + ModuleName name = readModuleName(in); + String *str = &stringTable[ReadVLN<Word>(in)]; + Word idx = ReadVLN<Word>(in); + + pd->functionByName.insert(new PrivData::FuncElem{{name, str}, idx}); + } + + // Function vector. + auto oldTable = pd->functionByIdx; + + pd->functionByIdx.clear(); + pd->functionByIdx.resize(ReadVLN<std::size_t>(in), nullptr); + funcV = pd->functionByIdx.data(); + funcC = pd->functionByIdx.size(); + + // Reset function indexes. + for(Function *&func : oldTable) + { + if(func) + { + auto idx = pd->functionByName.find({func->module->name, func->name}); + func->idx = idx ? idx->val : 0; + pd->functionByIdx[func->idx] = func; + } + } + } + + // + // Environment::loadGlobalScopes + // + void Environment::loadGlobalScopes(Serial &in) + { + // Clear existing scopes. + pd->scopes.free(); + + for(auto n = ReadVLN<std::size_t>(in); n--;) + getGlobalScope(ReadVLN<Word>(in))->loadState(in); + } + + // + // Environment::loadScriptActions + // + void Environment::loadScriptActions(Serial &in) + { + readScriptActions(in, scriptAction); + } + + // + // Environment::loadState + // + void Environment::loadState(Serial &in) + { + in.readSign(Signature::Environment); + + loadStringTable(in); + loadFunctions(in); + loadGlobalScopes(in); + loadScriptActions(in); + + in.readSign(~Signature::Environment); + } + + // + // Environment::loadStringTable + // + void Environment::loadStringTable(Serial &in) + { + StringTable oldTable{std::move(stringTable)}; + stringTable.loadState(in); + resetStrings(); + } + + // + // Environment::printArray + // + void Environment::printArray(PrintBuf &buf, Array const &array, Word index, Word limit) + { + PrintArrayChar(buf, array, index, limit); + } + + // + // Environment::printKill + // + void Environment::printKill(Thread *thread, Word type, Word data) + { + std::cerr << "ACSVM ERROR: Kill " << type << ':' << data + << " at " << (thread->codePtr - thread->module->codeV.data() - 1) << '\n'; + } + + // + // Environment::readModuleName + // + ModuleName Environment::readModuleName(Serial &in) const + { + auto s = readString(in); + auto i = ReadVLN<std::size_t>(in); + + return {s, nullptr, i}; + } + + // + // Environment::readScript + // + Script *Environment::readScript(Serial &in) const + { + auto idx = ReadVLN<std::size_t>(in); + return &findModule(readModuleName(in))->scriptV[idx]; + } + + // + // Environment::readScriptAction + // + ScriptAction *Environment::readScriptAction(Serial &in) const + { + auto action = static_cast<ScriptAction::Action>(ReadVLN<int>(in)); + + Vector<Word> argV; + argV.alloc(ReadVLN<std::size_t>(in)); + for(auto &arg : argV) + arg = ReadVLN<Word>(in); + + ScopeID id; + id.global = ReadVLN<Word>(in); + id.hub = ReadVLN<Word>(in); + id.map = ReadVLN<Word>(in); + + ScriptName name = readScriptName(in); + + return new ScriptAction{id, name, action, std::move(argV)}; + } + + // + // Environment::readScriptActions + // + void Environment::readScriptActions(Serial &in, ListLink<ScriptAction> &out) const + { + // Clear existing actions. + while(out.next->obj) + delete out.next->obj; + + for(auto n = ReadVLN<std::size_t>(in); n--;) + readScriptAction(in)->link.insert(&out); + } + + // + // Environment::readScriptName + // + ScriptName Environment::readScriptName(Serial &in) const + { + String *s = in.in->get() ? &stringTable[ReadVLN<Word>(in)] : nullptr; + Word i = ReadVLN<Word>(in); + return {s, i}; + } + + // + // Environment::readString + // + String *Environment::readString(Serial &in) const + { + if(auto idx = ReadVLN<std::size_t>(in)) + return &stringTable[idx - 1]; + else + return nullptr; + } + + // + // Environment::refStrings + // + void Environment::refStrings() + { + for(auto &action : scriptAction) + action.refStrings(this); + + for(auto &funcIdx : pd->functionByName) + { + funcIdx.key.first.s->ref = true; + funcIdx.key.second->ref = true; + } + + for(auto &module : pd->modules) + module.refStrings(); + + for(auto &scope : pd->scopes) + scope.refStrings(); + } + + // + // Environment::resetStrings + // + void Environment::resetStrings() + { + for(auto &funcIdx : pd->functionByName) + { + funcIdx.key.first.s = getString(funcIdx.key.first.s); + funcIdx.key.second = getString(funcIdx.key.second); + } + + for(auto &module : pd->modules) + module.resetStrings(); + } + + // + // Environment::saveFunctions + // + void Environment::saveFunctions(Serial &out) const + { + WriteVLN(out, pd->functionByName.size()); + for(auto &funcIdx : pd->functionByName) + { + writeModuleName(out, funcIdx.key.first); + WriteVLN(out, funcIdx.key.second->idx); + WriteVLN(out, funcIdx.val); + } + + WriteVLN(out, pd->functionByIdx.size()); + } + + // + // Environment::saveGlobalScopes + // + void Environment::saveGlobalScopes(Serial &out) const + { + WriteVLN(out, pd->scopes.size()); + for(auto &scope : pd->scopes) + { + WriteVLN(out, scope.id); + scope.saveState(out); + } + } + + // + // Environment::saveScriptActions + // + void Environment::saveScriptActions(Serial &out) const + { + writeScriptActions(out, scriptAction); + } + + // + // Environment::saveState + // + void Environment::saveState(Serial &out) const + { + out.writeSign(Signature::Environment); + + saveStringTable(out); + saveFunctions(out); + saveGlobalScopes(out); + saveScriptActions(out); + + out.writeSign(~Signature::Environment); + } + + // + // Environment::saveStringTable + // + void Environment::saveStringTable(Serial &out) const + { + stringTable.saveState(out); + } + + // + // Environment::writeModuleName + // + void Environment::writeModuleName(Serial &out, ModuleName const &in) const + { + writeString(out, in.s); + WriteVLN(out, in.i); + } + + // + // Environment::writeScript + // + void Environment::writeScript(Serial &out, Script *in) const + { + WriteVLN(out, in - in->module->scriptV.data()); + writeModuleName(out, in->module->name); + } + + // + // Environment::writeScriptAction + // + void Environment::writeScriptAction(Serial &out, ScriptAction const *in) const + { + WriteVLN<int>(out, in->action); + + WriteVLN(out, in->argV.size()); + for(auto &arg : in->argV) + WriteVLN(out, arg); + + WriteVLN(out, in->id.global); + WriteVLN(out, in->id.hub); + WriteVLN(out, in->id.map); + + writeScriptName(out, in->name); + } + + // + // Environment::writeScriptActions + // + void Environment::writeScriptActions(Serial &out, + ListLink<ScriptAction> const &in) const + { + WriteVLN(out, in.size()); + + for(auto &action : in) + writeScriptAction(out, &action); + } + + // + // Environment::writeScriptName + // + void Environment::writeScriptName(Serial &out, ScriptName const &in) const + { + if(in.s) + { + out.out->put('\1'); + WriteVLN(out, in.s->idx); + } + else + out.out->put('\0'); + + WriteVLN(out, in.i); + } + + // + // Environment::writeString + // + void Environment::writeString(Serial &out, String const *in) const + { + if(in) + WriteVLN<std::size_t>(out, in->idx + 1); + else + WriteVLN<std::size_t>(out, 0); + } + + // + // Environment::PrintArrayChar + // + void Environment::PrintArrayChar(PrintBuf &buf, Array const &array, Word index, Word limit) + { + // Calculate output length and end index. + std::size_t len = 0; + Word end; + for(Word &itr = end = index; itr - index != limit; ++itr) + { + Word c = array.find(itr); + if(!c) break; + ++len; + } + + // Acquire output buffer. + buf.reserve(len); + char *s = buf.getBuf(len); + + // Truncate elements to char. + for(Word itr = index; itr != end; ++itr) + *s++ = array.find(itr); + } + + // + // Environment::PrintArrayUTF8 + // + void Environment::PrintArrayUTF8(PrintBuf &buf, Array const &array, Word index, Word limit) + { + // Calculate output length and end index. + std::size_t len = 0; + Word end; + for(Word &itr = end = index; itr - index != limit; ++itr) + { + Word c = array.find(itr); + if(!c) break; + if(c > 0x10FFFF) c = 0xFFFD; + + if(c <= 0x007F) len += 1; + else if(c <= 0x07FF) len += 2; + else if(c <= 0xFFFF) len += 3; + else len += 4; + } + + // Acquire output buffer. + buf.reserve(len); + char *s = buf.getBuf(len); + + // Convert UTF-32 sequence to UTF-8. + for(Word itr = index; itr != end; ++itr) + { + Word c = array.find(itr); + if(c > 0x10FFFF) c = 0xFFFD; + + if(c <= 0x7F) {*s++ = 0x00 | (c >> 0); goto put0;} + if(c <= 0x7FF) {*s++ = 0xC0 | (c >> 6); goto put1;} + if(c <= 0xFFFF) {*s++ = 0xE0 | (c >> 12); goto put2;} + {*s++ = 0xF0 | (c >> 18); goto put3;} + + put3: *s++ = 0x80 | ((c >> 12) & 0x3F); + put2: *s++ = 0x80 | ((c >> 6) & 0x3F); + put1: *s++ = 0x80 | ((c >> 0) & 0x3F); + put0:; + } + } +} + +// EOF + diff --git a/src/acs/vm/ACSVM/Environment.hpp b/src/acs/vm/ACSVM/Environment.hpp new file mode 100644 index 0000000000000000000000000000000000000000..975a5af88375b9880e29ac9b1a6b1fb5f6c7a296 --- /dev/null +++ b/src/acs/vm/ACSVM/Environment.hpp @@ -0,0 +1,203 @@ +//----------------------------------------------------------------------------- +// +// Copyright (C) 2015-2017 David Hill +// +// See COPYING for license information. +// +//----------------------------------------------------------------------------- +// +// Environment class. +// +//----------------------------------------------------------------------------- + +#ifndef ACSVM__Environment_H__ +#define ACSVM__Environment_H__ + +#include "List.hpp" +#include "String.hpp" + + +//----------------------------------------------------------------------------| +// Types | +// + +namespace ACSVM +{ + // + // Environment + // + // Represents an entire ACS environment. + // + class Environment + { + public: + Environment(); + virtual ~Environment(); + + Word addCallFunc(CallFunc func); + + void addCodeDataACS0(Word code, CodeDataACS0 &&data); + void addFuncDataACS0(Word func, FuncDataACS0 &&data); + + virtual bool callFunc(Thread *thread, Word func, Word const *argV, Word argC); + Word callSpec(Thread *thread, Word spec, Word const *argV, Word argC); + + // Function to check if a lock can be opened. Default behavior is to + // always return false. + virtual bool checkLock(Thread *thread, Word lock, bool door); + + // Function to check tags. Must return true to indicate script should + // continue. Default behavior is to always return false. + virtual bool checkTag(Word type, Word tag); + + void collectStrings(); + + std::size_t countActiveThread() const; + + void deferAction(ScriptAction &&action); + + virtual void exec(); + + CodeDataACS0 const *findCodeDataACS0(Word code); + FuncDataACS0 const *findFuncDataACS0(Word func); + + Module *findModule(ModuleName const &name) const; + + // Used by Module when unloading. + void freeFunction(Function *func); + + void freeGlobalScope(GlobalScope *scope); + + void freeModule(Module *module); + + void freeThread(Thread *thread); + + CodeData const *getCodeData(Code code); + + Thread *getFreeThread(); + + Function *getFunction(Word idx) {return idx < funcC ? funcV[idx] : nullptr;} + + Function *getFunction(Module *module, String *name); + + GlobalScope *getGlobalScope(Word id); + + // Gets the named module, loading it if needed. + Module *getModule(ModuleName const &name); + + ModuleName getModuleName(char const *str); + virtual ModuleName getModuleName(char const *str, std::size_t len); + + // Called to translate script type from ACS0 script number. + // Default behavior is to modulus 1000 the name. + virtual std::pair<Word /*type*/, Word /*name*/> getScriptTypeACS0(Word name) + {return {name / 1000, name % 1000};} + + // Called to translate script type from ACSE SPTR. + // Default behavior is to return the type as-is. + virtual Word getScriptTypeACSE(Word type) {return type;} + + String *getString(Word idx) {return &stringTable[~idx];} + + String *getString(char const *first, char const *last) + {return &stringTable[{first, last}];} + + String *getString(char const *str) + {return getString(str, std::strlen(str));} + + String *getString(char const *str, std::size_t len) + {return &stringTable[{str, len}];} + + String *getString(StringData const *data) + {return data ? &stringTable[*data] : nullptr;} + + // Returns true if any contained scope is active and has an active thread. + bool hasActiveThread() const; + + virtual void loadState(Serial &in); + + // Prints an array to a print buffer. Default behavior is PrintArrayChar. + virtual void printArray(PrintBuf &buf, Array const &array, Word index, Word limit); + + // Function to print Kill instructions. Default behavior is to print + // message to stderr. + virtual void printKill(Thread *thread, Word type, Word data); + + // Deserializes a ModuleName. Default behavior is to load s and i. + virtual ModuleName readModuleName(Serial &in) const; + + Script *readScript(Serial &in) const; + ScriptAction *readScriptAction(Serial &in) const; + void readScriptActions(Serial &in, ListLink<ScriptAction> &out) const; + ScriptName readScriptName(Serial &in) const; + String *readString(Serial &in) const; + + virtual void refStrings(); + + virtual void resetStrings(); + + virtual void saveState(Serial &out) const; + + // Serializes a ModuleName. Default behavior is to save s and i. + virtual void writeModuleName(Serial &out, ModuleName const &name) const; + + void writeScript(Serial &out, Script *in) const; + void writeScriptAction(Serial &out, ScriptAction const *in) const; + void writeScriptActions(Serial &out, ListLink<ScriptAction> const &in) const; + void writeScriptName(Serial &out, ScriptName const &in) const; + void writeString(Serial &out, String const *in) const; + + StringTable stringTable; + + // Number of branches allowed per call to Thread::exec. Default of 0 + // means no limit. + Word branchLimit; + + // Default number of script variables. Default is 20. + Word scriptLocRegC; + + + // Prints an array to a print buffer, truncating elements of the array to + // fit char. + static void PrintArrayChar(PrintBuf &buf, Array const &array, Word index, Word limit); + + // Prints an array to a print buffer, converting the array as a UTF-32 + // sequence into a UTF-8 sequence. + static void PrintArrayUTF8(PrintBuf &buf, Array const &array, Word index, Word limit); + + static constexpr Word ScriptLocRegCDefault = 20; + + protected: + virtual Thread *allocThread(); + + // Called by callSpec after processing arguments. Default behavior is to + // do nothing and return 0. + virtual Word callSpecImpl(Thread *thread, Word spec, Word const *argV, Word argC); + + virtual void loadModule(Module *module) = 0; + + ListLink<ScriptAction> scriptAction; + ListLink<Thread> threadFree; + + Function **funcV; + std::size_t funcC; + + private: + struct PrivData; + + void loadFunctions(Serial &in); + void loadGlobalScopes(Serial &in); + void loadScriptActions(Serial &in); + void loadStringTable(Serial &in); + + void saveFunctions(Serial &out) const; + void saveGlobalScopes(Serial &out) const; + void saveScriptActions(Serial &out) const; + void saveStringTable(Serial &out) const; + + PrivData *pd; + }; +} + +#endif//ACSVM__Environment_H__ + diff --git a/src/acs/vm/ACSVM/Error.cpp b/src/acs/vm/ACSVM/Error.cpp new file mode 100644 index 0000000000000000000000000000000000000000..85de5cbbd14421282882db4417605322f757591d --- /dev/null +++ b/src/acs/vm/ACSVM/Error.cpp @@ -0,0 +1,56 @@ +//----------------------------------------------------------------------------- +// +// Copyright (C) 2017 David Hill +// +// See COPYING for license information. +// +//----------------------------------------------------------------------------- +// +// Error classes. +// +//----------------------------------------------------------------------------- + +#include "Error.hpp" + +#include "BinaryIO.hpp" + +#include <cctype> +#include <cstdio> + + +//----------------------------------------------------------------------------| +// Extern Functions | +// + +namespace ACSVM +{ + // + // SerialSignError constructor + // + SerialSignError::SerialSignError(Signature sig_, Signature got_) + { + auto sig = static_cast<std::uint32_t>(sig_); + auto got = static_cast<std::uint32_t>(got_); + + WriteLE4(reinterpret_cast<Byte *>(buf + SigS), sig); + WriteLE4(reinterpret_cast<Byte *>(buf + GotS), got); + for(auto i : {SigS+0, SigS+1, SigS+2, SigS+3, GotS+0, GotS+1, GotS+2, GotS+3}) + if(!std::isprint(buf[i]) && !std::isprint(buf[i] = ~buf[i])) buf[i] = ' '; + + for(int i = 8; i--;) buf[Sig + i] = "0123456789ABCDEF"[sig & 0xF], sig >>= 4; + for(int i = 8; i--;) buf[Got + i] = "0123456789ABCDEF"[got & 0xF], got >>= 4; + + msg = buf; + } + + // + // SerialSignError destructor + // + SerialSignError::~SerialSignError() + { + delete[] msg; + } +} + +// EOF + diff --git a/src/acs/vm/ACSVM/Error.hpp b/src/acs/vm/ACSVM/Error.hpp new file mode 100644 index 0000000000000000000000000000000000000000..6d217a11158b531c005e5f0517486821e6ab642a --- /dev/null +++ b/src/acs/vm/ACSVM/Error.hpp @@ -0,0 +1,81 @@ +//----------------------------------------------------------------------------- +// +// Copyright (C) 2015-2017 David Hill +// +// See COPYING for license information. +// +//----------------------------------------------------------------------------- +// +// Error classes. +// +//----------------------------------------------------------------------------- + +#ifndef ACSVM__Error_H__ +#define ACSVM__Error_H__ + +#include "Types.hpp" + +#include <stdexcept> + + +//----------------------------------------------------------------------------| +// Types | +// + +namespace ACSVM +{ + // + // ReadError + // + // Generic exception class for errors occurring during bytecode reading. + // + class ReadError : public std::exception + { + public: + ReadError(char const *msg_ = "ACSVM::ReadError") : msg{msg_} {} + + virtual char const *what() const noexcept {return msg;} + + char const *const msg; + }; + + // + // SerialError + // + // Generic exception for errors during serialization. + // + class SerialError : public std::exception + { + public: + SerialError(char const *msg_) : msg{const_cast<char *>(msg_)} {} + + virtual char const *what() const noexcept {return msg;} + + protected: + SerialError() : msg{nullptr} {} + + char *msg; + }; + + // + // SerialSignError + // + // Thrown due to signature mismatch. + // + class SerialSignError : public SerialError + { + public: + SerialSignError(Signature sig, Signature got); + ~SerialSignError(); + + private: + static constexpr std::size_t Sig = 29, SigS = 39; + static constexpr std::size_t Got = 49, GotS = 59; + + char buf[sizeof("signature mismatch: expected XXXXXXXX (XXXX) got XXXXXXXX (XXXX)")] = + "signature mismatch: expected XXXXXXXX (XXXX) got XXXXXXXX (XXXX)"; + }; +} + +#endif//ACSVM__Error_H__ + diff --git a/src/acs/vm/ACSVM/Function.cpp b/src/acs/vm/ACSVM/Function.cpp new file mode 100644 index 0000000000000000000000000000000000000000..53e5e8f6c65d47475b706e5bb0c3ba0895213a35 --- /dev/null +++ b/src/acs/vm/ACSVM/Function.cpp @@ -0,0 +1,48 @@ +//----------------------------------------------------------------------------- +// +// Copyright (C) 2015 David Hill +// +// See COPYING for license information. +// +//----------------------------------------------------------------------------- +// +// Function class. +// +//----------------------------------------------------------------------------- + +#include "Function.hpp" + + +//----------------------------------------------------------------------------| +// Extern Functions | +// + +namespace ACSVM +{ + // + // Function constructor + // + Function::Function(Module *module_, String *name_, Word idx_) : + module{module_}, + name {name_}, + idx {idx_}, + + argC {0}, + codeIdx{0}, + locArrC{0}, + locRegC{0}, + + flagRet{false} + { + } + + // + // Function destructor + // + Function::~Function() + { + } +} + +// EOF + diff --git a/src/acs/vm/ACSVM/Function.hpp b/src/acs/vm/ACSVM/Function.hpp new file mode 100644 index 0000000000000000000000000000000000000000..2186d537ba7aa6f26484beda728f03adf417d88e --- /dev/null +++ b/src/acs/vm/ACSVM/Function.hpp @@ -0,0 +1,48 @@ +//----------------------------------------------------------------------------- +// +// Copyright (C) 2015 David Hill +// +// See COPYING for license information. +// +//----------------------------------------------------------------------------- +// +// Function class. +// +//----------------------------------------------------------------------------- + +#ifndef ACSVM__Function_H__ +#define ACSVM__Function_H__ + +#include "Types.hpp" + + +//----------------------------------------------------------------------------| +// Types | +// + +namespace ACSVM +{ + // + // Function + // + class Function + { + public: + Function(Module *module, String *name, Word idx); + ~Function(); + + Module *module; + String *name; + Word idx; + + Word argC; + Word codeIdx; + Word locArrC; + Word locRegC; + + bool flagRet : 1; + }; +} + +#endif//ACSVM__Function_H__ + diff --git a/src/acs/vm/ACSVM/HashMap.hpp b/src/acs/vm/ACSVM/HashMap.hpp new file mode 100644 index 0000000000000000000000000000000000000000..b509e5119e10171b37c627be4442758aed461bf0 --- /dev/null +++ b/src/acs/vm/ACSVM/HashMap.hpp @@ -0,0 +1,278 @@ +//----------------------------------------------------------------------------- +// +// Copyright (C) 2015 David Hill +// +// See COPYING for license information. +// +//----------------------------------------------------------------------------- +// +// HashMap class. +// +//----------------------------------------------------------------------------- + +#ifndef ACSVM__HashMap_H__ +#define ACSVM__HashMap_H__ + +#include "List.hpp" +#include "Types.hpp" +#include "Vector.hpp" + +#include <functional> + + +//----------------------------------------------------------------------------| +// Types | +// + +namespace ACSVM +{ + // + // HashMapGetKeyMem + // + // Used for HashMaps for which the Key is a member of T. + // + template<typename Key, typename T, Key const T::*KeyMem> + struct HashMapGetKeyMem + { + static Key const &Get(T *obj) {return obj->*KeyMem;} + }; + + // + // HashMapGetKeyObj + // + // Used for HashMaps for which the Key is a base class or the same as T. + // + template<typename Key, typename T> + struct HashMapGetKeyObj + { + static Key const &Get(T *obj) {return *obj;} + }; + + // + // HashMap + // + // Stores objects of type T that can be found by keys of type Key. + // + // GetKeyMem must be HashMapGetKeyMem or HashMapGetKeyObj. + // + // This class does not manage the lifetimes of the contained objects, and + // objects must be unlinked by the unlink function before being destructed. + // Although an exception is made to the latter if the clear function is + // called before any other. + // + template<typename Key, typename T, typename GetKey, ListLink<T> T::*LinkMem, + typename Hash = std::hash<Key>, typename KeyEqual = std::equal_to<Key>> + class HashMap + { + private: + // + // Iterator + // + template<typename Obj> + class IteratorBase + { + public: + // + // operator ++ + // + IteratorBase<Obj> &operator ++ () + { + for(;;) + { + // Traverse to next link. + link = link->next; + + // If it has an object, we are done. + if(link->obj) break; + + // Otherwise, we are at the current chain's head. So increment + // to the next chain. If at the last chain, we are done. + if(++link == last) break; + } + + return *this; + } + + IteratorBase<Obj> operator ++ (int) {auto i = *this; ++*this; return i;} + + Obj &operator * () const {return *link->obj;} + Obj *operator -> () const {return link->obj;} + + bool operator == (IteratorBase<Obj> const &iter) const + {return iter.link == link;} + bool operator != (IteratorBase<Obj> const &iter) const + {return iter.link != link;} + + + friend class HashMap; + + private: + IteratorBase(ListLink<T> *link_, ListLink<T> *last_) : + link{link_}, last{last_} {if(link != last) ++*this;} + + ListLink<T> *link, *last; + }; + + public: + using const_iterator = IteratorBase<T const>; + using iterator = IteratorBase<T>; + using size_type = std::size_t; + + + HashMap() : chainV{16}, objC{0}, growC{16} {} + HashMap(size_type count, size_type growC_) : + chainV{count}, objC{0}, growC{growC_} {} + ~HashMap() {clear();} + + // begin + iterator begin() {return {chainV.begin(), chainV.end()};} + + // + // clear + // + void clear() + { + for(auto &chain : chainV) + { + while(auto obj = chain.next->obj) + (obj->*LinkMem).unlink(); + } + + objC = 0; + } + + // end + iterator end() {return {chainV.end(), chainV.end()};} + + // + // find + // + T *find(Key const &key) + { + for(auto itr = chainV[hasher(key) % chainV.size()].next; itr->obj; itr = itr->next) + { + if(equal(key, GetKey::Get(itr->obj))) + return itr->obj; + } + + return nullptr; + } + + // + // free + // + // Unlinks and deletes all contained objects. + // + void free() + { + for(auto &chain : chainV) + { + while(auto obj = chain.next->obj) + (obj->*LinkMem).unlink(), delete obj; + } + + objC = 0; + } + + // + // insert + // + void insert(T *obj) + { + if(objC >= chainV.size()) + resize(chainV.size() + chainV.size() / 2 + growC); + + ++objC; + (obj->*LinkMem).insert(&chainV[hasher(GetKey::Get(obj)) % chainV.size()]); + } + + // + // resize + // + // Reallocates to count chains. + // + void resize(size_type count) + { + auto oldChainV = std::move(chainV); + chainV.alloc(count); + + for(auto &chain : oldChainV) + { + while(auto obj = chain.next->obj) + (obj->*LinkMem).relink(&chainV[hasher(GetKey::Get(obj)) % chainV.size()]); + } + } + + // size + size_type size() const {return objC;} + + // + // unlink + // + void unlink(T *obj) + { + --objC; + (obj->*LinkMem).unlink(); + } + + private: + Vector<ListLink<T>> chainV; + Hash hasher; + KeyEqual equal; + + size_type objC; + size_type growC; + }; + + // + // HashMapKeyMem + // + // Convenience typedef for HashMapGetKeyMem-based HashMaps. + // + template<typename Key, typename T, Key const T::*KeyMem, + ListLink<T> T::*LinkMem, typename Hash = std::hash<Key>, + typename KeyEqual = std::equal_to<Key>> + using HashMapKeyMem = HashMap<Key, T, HashMapGetKeyMem<Key, T, KeyMem>, + LinkMem, Hash, KeyEqual>; + + // + // HashMapKeyObj + // + // Convenience typedef for HashMapGetKeyObj-based HashMaps. + // + template<typename Key, typename T, ListLink<T> T::*LinkMem, + typename Hash = std::hash<Key>, typename KeyEqual = std::equal_to<Key>> + using HashMapKeyObj = HashMap<Key, T, HashMapGetKeyObj<Key, T>, LinkMem, + Hash, KeyEqual>; + + // + // HashMapElem + // + // Wraps a type with a key and link for use in HashMap. + // + template<typename Key, typename T> + class HashMapElem + { + public: + HashMapElem(Key const &key_, T const &val_) : + key{key_}, val{val_}, link{this} {} + + Key key; + T val; + + ListLink<HashMapElem<Key, T>> link; + }; + + // + // HashMapKeyExt + // + // Convenience typedef for HashMapElem-based HashMaps. + // + template<typename Key, typename T, typename Hash = std::hash<Key>, + typename KeyEqual = std::equal_to<Key>> + using HashMapKeyExt = HashMapKeyMem<Key, HashMapElem<Key, T>, + &HashMapElem<Key, T>::key, &HashMapElem<Key, T>::link, Hash, KeyEqual>; +} + +#endif//ACSVM__HashMap_H__ + diff --git a/src/acs/vm/ACSVM/HashMapFixed.hpp b/src/acs/vm/ACSVM/HashMapFixed.hpp new file mode 100644 index 0000000000000000000000000000000000000000..0886d9b97dfa2cc477c1829c646fda12d0390df0 --- /dev/null +++ b/src/acs/vm/ACSVM/HashMapFixed.hpp @@ -0,0 +1,144 @@ +//----------------------------------------------------------------------------- +// +// Copyright (C) 2015 David Hill +// +// See COPYING for license information. +// +//----------------------------------------------------------------------------- +// +// HashMapFixed class. +// +//----------------------------------------------------------------------------- + +#ifndef ACSVM__HashMapFixed_H__ +#define ACSVM__HashMapFixed_H__ + +#include "Types.hpp" + +#include <functional> +#include <new> + + +//----------------------------------------------------------------------------| +// Types | +// + +namespace ACSVM +{ + // + // HashMapFixed + // + // Non-resizable hash map. + // + template<typename Key, typename T, typename Hash = std::hash<Key>> + class HashMapFixed + { + public: + struct Elem + { + Key key; + T val; + Elem *next; + }; + + using iterator = Elem *; + using size_type = std::size_t; + using value_type = Elem; + + + HashMapFixed() : hasher{}, table{nullptr}, elemV{nullptr}, elemC{0} {} + ~HashMapFixed() {free();} + + // + // alloc + // + void alloc(size_type count) + { + if(elemV) free(); + + if(!count) return; + + size_type sizeRaw = sizeof(Elem) * count + sizeof(Elem *) * count; + + elemC = count; + elemV = static_cast<Elem *>(::operator new(sizeRaw)); + } + + // begin + iterator begin() {return elemV;} + + // + // build + // + void build() + { + // Initialize table. + table = reinterpret_cast<Elem **>(elemV + elemC); + for(Elem **elem = table + elemC; elem != table;) + *--elem = nullptr; + + // Insert elements. + for(Elem &elem : *this) + { + size_type hash = hasher(elem.key) % elemC; + + elem.next = table[hash]; + table[hash] = &elem; + } + } + + // empty + bool empty() const {return !elemC;} + + // end + iterator end() {return elemV + elemC;} + + // + // find + // + T *find(Key const &key) + { + if(!table) return nullptr; + + for(Elem *elem = table[hasher(key) % elemC]; elem; elem = elem->next) + { + if(elem->key == key) + return &elem->val; + } + + return nullptr; + } + + // + // free + // + void free() + { + if(table) + { + for(Elem *elem = elemV + elemC; elem != elemV;) + (--elem)->~Elem(); + + table = nullptr; + } + + ::operator delete(elemV); + + elemV = nullptr; + elemC = 0; + } + + // size + size_type size() const {return elemC;} + + private: + Hash hasher; + + Elem **table; + Elem *elemV; + size_type elemC; + }; +} + +#endif//ACSVM__HashMapFixed_H__ + diff --git a/src/acs/vm/ACSVM/ID.hpp b/src/acs/vm/ACSVM/ID.hpp new file mode 100644 index 0000000000000000000000000000000000000000..fbb113cce0e457519b7be27ac3b749d43257e90d --- /dev/null +++ b/src/acs/vm/ACSVM/ID.hpp @@ -0,0 +1,50 @@ +//----------------------------------------------------------------------------- +// +// Copyright (C) 2017 David Hill +// +// See COPYING for license information. +// +//----------------------------------------------------------------------------- +// +// Numeric identifiers. +// +//----------------------------------------------------------------------------- + +#ifndef ACSVM__ID_H__ +#define ACSVM__ID_H__ + +#include "Types.hpp" + + +//----------------------------------------------------------------------------| +// Extern Functions | +// + +namespace ACSVM +{ + constexpr std::uint32_t MakeID(char c0, char c1, char c2, char c3); + constexpr std::uint32_t MakeID(char const (&s)[5]); + + // + // MakeID + // + constexpr std::uint32_t MakeID(char c0, char c1, char c2, char c3) + { + return + (static_cast<std::uint32_t>(c0) << 0) | + (static_cast<std::uint32_t>(c1) << 8) | + (static_cast<std::uint32_t>(c2) << 16) | + (static_cast<std::uint32_t>(c3) << 24); + } + + // + // MakeID + // + constexpr std::uint32_t MakeID(char const (&s)[5]) + { + return MakeID(s[0], s[1], s[2], s[3]); + } +} + +#endif//ACSVM__ID_H__ + diff --git a/src/acs/vm/ACSVM/Init.cpp b/src/acs/vm/ACSVM/Init.cpp new file mode 100644 index 0000000000000000000000000000000000000000..66e65bc8113d0b0701e1567aa5d8026ea621599f --- /dev/null +++ b/src/acs/vm/ACSVM/Init.cpp @@ -0,0 +1,149 @@ +//----------------------------------------------------------------------------- +// +// Copyright (C) 2015 David Hill +// +// See COPYING for license information. +// +//----------------------------------------------------------------------------- +// +// Initializer handling. +// +//----------------------------------------------------------------------------- + +#include "Init.hpp" + +#include "Array.hpp" +#include "Function.hpp" +#include "Module.hpp" +#include "String.hpp" + +#include <vector> + + +//----------------------------------------------------------------------------| +// Types | +// + +namespace ACSVM +{ + // + // ArrayInit::PrivData + // + struct ArrayInit::PrivData + { + std::vector<WordInit> initV; + }; +} + + +//----------------------------------------------------------------------------| +// Extern Functions | +// + +namespace ACSVM +{ + + // + // ArrayInit constructor + // + ArrayInit::ArrayInit() : + pd{new PrivData} + { + } + + // + // ArrayInit destructor + // + ArrayInit::~ArrayInit() + { + delete pd; + } + + // + // ArrayInit::apply + // + void ArrayInit::apply(Array &arr, Module *module) + { + Word idx = 0; + for(WordInit &init : pd->initV) + { + Word value = init.getValue(module); + if(value) arr[idx] = value; + ++idx; + } + } + + // + // ArrayInit::finish + // + void ArrayInit::finish() + { + // Clear out trailing zeroes. + while(!pd->initV.empty() && !pd->initV.back()) + pd->initV.pop_back(); + + // Shrink vector. + pd->initV.shrink_to_fit(); + + // TODO: Break up initialization data into nonzero ranges. + } + + // + // ArrayInit::reserve + // + void ArrayInit::reserve(Word count) + { + pd->initV.resize(count, 0); + } + + // + // ArrayInit::setTag + // + void ArrayInit::setTag(Word idx, InitTag tag) + { + if(idx >= pd->initV.size()) + pd->initV.resize(idx + 1, 0); + + pd->initV[idx].tag = tag; + } + + // + // ArrayInit::setVal + // + void ArrayInit::setVal(Word idx, Word val) + { + if(idx >= pd->initV.size()) + pd->initV.resize(idx + 1, 0); + + pd->initV[idx].val = val; + } + + // + // WordInit::getValue + // + Word WordInit::getValue(Module *module) const + { + switch(tag) + { + case InitTag::Integer: + return val; + + case InitTag::Function: + if(val < module->functionV.size() && module->functionV[val]) + return module->functionV[val]->idx; + else + return val; + + case InitTag::String: + if(val < module->stringV.size()) + return ~module->stringV[val]->idx; + else + return val; + } + + return val; + } +} + +// EOF + diff --git a/src/acs/vm/ACSVM/Init.hpp b/src/acs/vm/ACSVM/Init.hpp new file mode 100644 index 0000000000000000000000000000000000000000..aecbf700b9673b807173ac088d2ff24c60d2599d --- /dev/null +++ b/src/acs/vm/ACSVM/Init.hpp @@ -0,0 +1,80 @@ +//----------------------------------------------------------------------------- +// +// Copyright (C) 2015 David Hill +// +// See COPYING for license information. +// +//----------------------------------------------------------------------------- +// +// Initializer handling. +// +//----------------------------------------------------------------------------- + +#ifndef ACSVM__Init_H__ +#define ACSVM__Init_H__ + +#include "Types.hpp" + + +//----------------------------------------------------------------------------| +// Types | +// + +namespace ACSVM +{ + // + // InitTag + // + enum class InitTag + { + Integer, + Function, + String, + }; + + // + // ArrayInit + // + class ArrayInit + { + public: + ArrayInit(); + ~ArrayInit(); + + void apply(Array &arr, Module *module); + + void finish(); + + void reserve(Word count); + + void setTag(Word idx, InitTag tag); + void setVal(Word idx, Word val); + + private: + struct PrivData; + + PrivData *pd; + }; + + // + // WordInit + // + class WordInit + { + public: + WordInit() = default; + WordInit(Word val_) : val{val_}, tag{InitTag::Integer} {} + WordInit(Word val_, InitTag tag_) : val{val_}, tag{tag_} {} + + explicit operator bool () const + {return val || tag != InitTag::Integer;} + + Word getValue(Module *module) const; + + Word val; + InitTag tag; + }; +} + +#endif//ACSVM__Init_H__ + diff --git a/src/acs/vm/ACSVM/Jump.cpp b/src/acs/vm/ACSVM/Jump.cpp new file mode 100644 index 0000000000000000000000000000000000000000..fa6efed3789239195369ed473fa416290a4e302c --- /dev/null +++ b/src/acs/vm/ACSVM/Jump.cpp @@ -0,0 +1,44 @@ +//----------------------------------------------------------------------------- +// +// Copyright (C) 2015 David Hill +// +// See COPYING for license information. +// +//----------------------------------------------------------------------------- +// +// Jump class. +// +//----------------------------------------------------------------------------- + +#include "Jump.hpp" + +#include "BinaryIO.hpp" + + +//----------------------------------------------------------------------------| +// Extern Functions | +// + +namespace ACSVM +{ + // + // JumpMap::loadJumps + // + void JumpMap::loadJumps(Byte const *data, std::size_t count) + { + table.alloc(count); + std::size_t iter = 0; + + for(auto &jump : table) + { + Word caseVal = ReadLE4(data + iter); iter += 4; + Word codeIdx = ReadLE4(data + iter); iter += 4; + new(&jump) HashMapFixed<Word, Word>::Elem{caseVal, codeIdx, nullptr}; + } + + table.build(); + } +} + +// EOF + diff --git a/src/acs/vm/ACSVM/Jump.hpp b/src/acs/vm/ACSVM/Jump.hpp new file mode 100644 index 0000000000000000000000000000000000000000..1656de037aa2952cda4eeff771334854d7624502 --- /dev/null +++ b/src/acs/vm/ACSVM/Jump.hpp @@ -0,0 +1,49 @@ +//----------------------------------------------------------------------------- +// +// Copyright (C) 2015 David Hill +// +// See COPYING for license information. +// +//----------------------------------------------------------------------------- +// +// Jump class. +// +//----------------------------------------------------------------------------- + +#ifndef ACSVM__Jump_H__ +#define ACSVM__Jump_H__ + +#include "HashMapFixed.hpp" + + +//----------------------------------------------------------------------------| +// Types | +// + +namespace ACSVM +{ + // + // Jump + // + // Dynamic jump target. + // + class Jump + { + public: + Word codeIdx; + }; + + // + // JumpMap + // + class JumpMap + { + public: + void loadJumps(Byte const *data, std::size_t count); + + HashMapFixed<Word, Word> table; + }; +} + +#endif//ACSVM__Jump_H__ + diff --git a/src/acs/vm/ACSVM/List.hpp b/src/acs/vm/ACSVM/List.hpp new file mode 100644 index 0000000000000000000000000000000000000000..3c65a2a350a9cb91b4b0f8bffac1038ebd0fb6c0 --- /dev/null +++ b/src/acs/vm/ACSVM/List.hpp @@ -0,0 +1,114 @@ +//----------------------------------------------------------------------------- +// +// Copyright (C) 2015 David Hill +// +// See COPYING for license information. +// +//----------------------------------------------------------------------------- +// +// Linked list handling. +// +//----------------------------------------------------------------------------- + +#ifndef ACSVM__List_H__ +#define ACSVM__List_H__ + +#include "Types.hpp" + + +//----------------------------------------------------------------------------| +// Types | +// + +namespace ACSVM +{ + // + // ListLink + // + template<typename T> + class ListLink + { + private: + // + // IteratorBase + // + template<typename Obj> + class IteratorBase + { + public: + IteratorBase<Obj> &operator ++ () {link = link->next; return *this;} + IteratorBase<Obj> operator ++ (int) {auto i = *this; ++*this; return i;} + + Obj &operator * () const {return *link->obj;} + Obj *operator -> () const {return link->obj;} + + bool operator == (IteratorBase<Obj> const &iter) const + {return iter.link == link;} + bool operator != (IteratorBase<Obj> const &iter) const + {return iter.link != link;} + + + friend class ListLink; + + private: + IteratorBase(ListLink<T> const *link_) : link{link_} {} + + ListLink<T> const *link; + }; + + public: + ListLink() : obj{nullptr}, prev{this}, next{this} {} + ListLink(ListLink<T> const &) = delete; + ListLink(T *obj_) : obj{obj_}, prev{this}, next{this} {} + ListLink(T *obj_, ListLink<T> &&link) : + obj{obj_}, prev{link.prev}, next{link.next} + {prev->next = next->prev = this; link.prev = link.next = &link;} + ~ListLink() {unlink();} + + // begin + IteratorBase<T> begin() {return next;} + IteratorBase<T const> begin() const {return next;} + + // end + IteratorBase<T> end() {return this;} + IteratorBase<T const> end() const {return this;} + + // + // insert + // + void insert(ListLink<T> *head) + { + (prev = head->prev)->next = this; + (next = head )->prev = this; + } + + void relink(ListLink<T> *head) {unlink(); insert(head);} + + // + // size + // + std::size_t size() const + { + std::size_t count = 0; + for(auto const &o : *this) (void)o, ++count; + return count; + } + + // + // unlink + // + void unlink() + { + prev->next = next; + next->prev = prev; + + prev = next = this; + } + + T *const obj; + ListLink<T> *prev, *next; + }; +} + +#endif//ACSVM__List_H__ + diff --git a/src/acs/vm/ACSVM/Module.cpp b/src/acs/vm/ACSVM/Module.cpp new file mode 100644 index 0000000000000000000000000000000000000000..95626be7b5ac878c268d346e2704e7d752cc34e7 --- /dev/null +++ b/src/acs/vm/ACSVM/Module.cpp @@ -0,0 +1,139 @@ +//----------------------------------------------------------------------------- +// +// Copyright (C) 2015 David Hill +// +// See COPYING for license information. +// +//----------------------------------------------------------------------------- +// +// Module class. +// +//----------------------------------------------------------------------------- + +#include "Module.hpp" + +#include "Array.hpp" +#include "Environment.hpp" +#include "Function.hpp" +#include "Init.hpp" +#include "Jump.hpp" +#include "Script.hpp" + + +//----------------------------------------------------------------------------| +// Extern Functions | +// + +namespace ACSVM +{ + // + // ModuleName::hash + // + std::size_t ModuleName::hash() const + { + return s->hash + std::hash<void*>()(p) + i; + } + + // + // Module constructor + // + Module::Module(Environment *env_, ModuleName const &name_) : + env{env_}, + name{name_}, + + hashLink{this}, + + isACS0{false}, + loaded{false} + { + } + + // + // Module destructor + // + Module::~Module() + { + reset(); + } + + // + // Module::refStrings + // + void Module::refStrings() const + { + if(name.s) name.s->ref = true; + + for(auto &s : arrImpV) if(s) s->ref = true; + for(auto &s : arrNameV) if(s) s->ref = true; + for(auto &s : funcNameV) if(s) s->ref = true; + for(auto &s : regImpV) if(s) s->ref = true; + for(auto &s : regNameV) if(s) s->ref = true; + for(auto &s : scrNameV) if(s) s->ref = true; + for(auto &s : stringV) if(s) s->ref = true; + + for(auto &func : functionV) + if(func && func->name) func->name->ref = true; + + for(auto &scr : scriptV) + if(scr.name.s) scr.name.s->ref = true; + } + + // + // Module::reset + // + void Module::reset() + { + // Unload locally defined functions from env. + for(Function *&func : functionV) + { + if(func && func->module == this) + env->freeFunction(func); + } + + arrImpV.free(); + arrInitV.free(); + arrNameV.free(); + arrSizeV.free(); + codeV.free(); + funcNameV.free(); + functionV.free(); + importV.free(); + jumpV.free(); + jumpMapV.free(); + regImpV.free(); + regInitV.free(); + regNameV.free(); + scrNameV.free(); + scriptV.free(); + stringV.free(); + + isACS0 = false; + loaded = false; + } + + // + // Module::resetStrings + // + void Module::resetStrings() + { + name.s = env->getString(name.s); + + for(auto &s : arrImpV) s = env->getString(s); + for(auto &s : arrNameV) s = env->getString(s); + for(auto &s : funcNameV) s = env->getString(s); + for(auto &s : regImpV) s = env->getString(s); + for(auto &s : regNameV) s = env->getString(s); + for(auto &s : scrNameV) s = env->getString(s); + for(auto &s : stringV) s = env->getString(s); + + for(auto &func : functionV) + if(func) func->name = env->getString(func->name); + + for(auto &scr : scriptV) + scr.name.s = env->getString(scr.name.s); + } +} + + +// EOF + diff --git a/src/acs/vm/ACSVM/Module.hpp b/src/acs/vm/ACSVM/Module.hpp new file mode 100644 index 0000000000000000000000000000000000000000..b2b46e6132443842ea8d9165f9a76e9d9378e305 --- /dev/null +++ b/src/acs/vm/ACSVM/Module.hpp @@ -0,0 +1,178 @@ +//----------------------------------------------------------------------------- +// +// Copyright (C) 2015-2017 David Hill +// +// See COPYING for license information. +// +//----------------------------------------------------------------------------- +// +// Module class. +// +//----------------------------------------------------------------------------- + +#ifndef ACSVM__Module_H__ +#define ACSVM__Module_H__ + +#include "ID.hpp" +#include "List.hpp" +#include "Vector.hpp" + +#include <functional> +#include <memory> + + +//----------------------------------------------------------------------------| +// Types | +// + +namespace ACSVM +{ + // + // ModuleName + // + // Stores a Module's name. Name semantics are user-defined and must provide + // a (user-defined) mapping from name to bytecode data. The names are used + // internally only for determining if a specific module has already been + // loaded. That is, two ModuleNames should compare equal if and only if they + // designate the same bytecode data. + // + class ModuleName + { + public: + ModuleName(String *s_, void *p_, std::size_t i_) : s{s_}, p{p_}, i{i_} {} + + bool operator == (ModuleName const &name) const + {return s == name.s && p == name.p && i == name.i;} + bool operator != (ModuleName const &name) const + {return s != name.s || p != name.p || i != name.i;} + + std::size_t hash() const; + + // String value. May be null. + String *s; + + // Arbitrary pointer value. + void *p; + + // Arbitrary integer value. + std::size_t i; + }; + + // + // Module + // + // Represents an ACS bytecode module. + // + class Module + { + public: + Module(Environment *env, ModuleName const &name); + ~Module(); + + void readBytecode(Byte const *data, std::size_t size); + + void refStrings() const; + + void reset(); + + void resetStrings(); + + Environment *env; + ModuleName name; + + Vector<String *> arrImpV; + Vector<ArrayInit> arrInitV; + Vector<String *> arrNameV; + Vector<Word> arrSizeV; + Vector<Word> codeV; + Vector<String *> funcNameV; + Vector<Function *> functionV; + Vector<Module *> importV; + Vector<Jump> jumpV; + Vector<JumpMap> jumpMapV; + Vector<String *> regImpV; + Vector<WordInit> regInitV; + Vector<String *> regNameV; + Vector<String *> scrNameV; + Vector<Script> scriptV; + Vector<String *> stringV; + + ListLink<Module> hashLink; + + bool isACS0; + bool loaded; + + + static std::pair< + std::unique_ptr<Byte[]> /*data*/, + std::size_t /*size*/> + DecryptStringACSE(Byte const *data, std::size_t size, std::size_t iter); + + static std::unique_ptr<char[]> ParseStringACS0(Byte const *first, + Byte const *last, std::size_t len); + + static std::tuple< + Byte const * /*begin*/, + Byte const * /*end*/, + std::size_t /*len*/> + ScanStringACS0(Byte const *data, std::size_t size, std::size_t iter); + + private: + bool chunkIterACSE(Byte const *data, std::size_t size, + bool (Module::*chunker)(Byte const *, std::size_t, Word)); + + void chunkStrTabACSE(Vector<String *> &strV, + Byte const *data, std::size_t size, bool junk); + + bool chunkerACSE_AIMP(Byte const *data, std::size_t size, Word chunkName); + bool chunkerACSE_AINI(Byte const *data, std::size_t size, Word chunkName); + bool chunkerACSE_ARAY(Byte const *data, std::size_t size, Word chunkName); + bool chunkerACSE_ASTR(Byte const *data, std::size_t size, Word chunkName); + bool chunkerACSE_ATAG(Byte const *data, std::size_t size, Word chunkName); + bool chunkerACSE_FARY(Byte const *data, std::size_t size, Word chunkName); + bool chunkerACSE_FNAM(Byte const *data, std::size_t size, Word chunkName); + bool chunkerACSE_FUNC(Byte const *data, std::size_t size, Word chunkName); + bool chunkerACSE_JUMP(Byte const *data, std::size_t size, Word chunkName); + bool chunkerACSE_LOAD(Byte const *data, std::size_t size, Word chunkName); + bool chunkerACSE_MEXP(Byte const *data, std::size_t size, Word chunkName); + bool chunkerACSE_MIMP(Byte const *data, std::size_t size, Word chunkName); + bool chunkerACSE_MINI(Byte const *data, std::size_t size, Word chunkName); + bool chunkerACSE_MSTR(Byte const *data, std::size_t size, Word chunkName); + bool chunkerACSE_SARY(Byte const *data, std::size_t size, Word chunkName); + bool chunkerACSE_SFLG(Byte const *data, std::size_t size, Word chunkName); + bool chunkerACSE_SNAM(Byte const *data, std::size_t size, Word chunkName); + bool chunkerACSE_SPTR8(Byte const *data, std::size_t size, Word chunkName); + bool chunkerACSE_SPTR12(Byte const *data, std::size_t size, Word chunkName); + bool chunkerACSE_STRE(Byte const *data, std::size_t size, Word chunkName); + bool chunkerACSE_STRL(Byte const *data, std::size_t size, Word chunkName); + bool chunkerACSE_SVCT(Byte const *data, std::size_t size, Word chunkName); + + void readBytecodeACS0(Byte const *data, std::size_t size); + void readBytecodeACSE(Byte const *data, std::size_t size, + bool compressed, std::size_t iter = 4); + + void readChunksACSE(Byte const *data, std::size_t size, bool fakeACS0); + + void readCodeACS0(Byte const *data, std::size_t size, bool compressed); + + String *readStringACS0(Byte const *data, std::size_t size, std::size_t iter); + + void setScriptNameTypeACSE(Script *scr, Word nameInt, Word type); + }; +} + +namespace std +{ + // + // hash<::ACSVM::ModuleName> + // + template<> + struct hash<::ACSVM::ModuleName> + { + size_t operator () (::ACSVM::ModuleName const &name) const + {return name.hash();} + }; +} + +#endif//ACSVM__Module_H__ + diff --git a/src/acs/vm/ACSVM/ModuleACS0.cpp b/src/acs/vm/ACSVM/ModuleACS0.cpp new file mode 100644 index 0000000000000000000000000000000000000000..7ae16ac2f2efd1ce0bd4d16b1a8a3f4342984907 --- /dev/null +++ b/src/acs/vm/ACSVM/ModuleACS0.cpp @@ -0,0 +1,333 @@ +//----------------------------------------------------------------------------- +// +// Copyright (C) 2015-2017 David Hill +// +// See COPYING for license information. +// +//----------------------------------------------------------------------------- +// +// Module class bytecode reading. +// +//----------------------------------------------------------------------------- + +#include "Module.hpp" + +#include "BinaryIO.hpp" +#include "Environment.hpp" +#include "Error.hpp" +#include "Jump.hpp" +#include "Script.hpp" +#include "Tracer.hpp" + + +//----------------------------------------------------------------------------| +// Extern Functions | +// + +namespace ACSVM +{ + // + // Module::reaBytecode + // + void Module::readBytecode(Byte const *data, std::size_t size) + { + try + { + if(size < 4) throw ReadError(); + + switch(ReadLE4(data)) + { + case MakeID("ACS\0"): + readBytecodeACS0(data, size); + break; + + case MakeID("ACSE"): + readBytecodeACSE(data, size, false); + break; + + case MakeID("ACSe"): + readBytecodeACSE(data, size, true); + break; + } + } + catch(...) + { + // If an exception occurs before module is fully loaded, reset it. + if(!loaded) + reset(); + + throw; + } + } + + // + // Module::readBytecodeACS0 + // + void Module::readBytecodeACS0(Byte const *data, std::size_t size) + { + std::size_t iter; + + // Read table index. + if(size < 8) throw ReadError(); + iter = ReadLE4(data + 4); + if(iter > size) throw ReadError(); + + // Check for ACSE header behind indicated table. + if(iter >= 8) + { + switch(ReadLE4(data + (iter - 4))) + { + case MakeID("ACSE"): + return readBytecodeACSE(data, size, false, iter - 8); + + case MakeID("ACSe"): + return readBytecodeACSE(data, size, true, iter - 8); + } + } + + // Mark as ACS0. + isACS0 = true; + + // Read script table. + + // Read script count. + if(size - iter < 4) throw ReadError(); + scriptV.alloc(ReadLE4(data + iter), this); iter += 4; + + // Read scripts. + if(size - iter < scriptV.size() * 12) throw ReadError(); + for(Script &scr : scriptV) + { + scr.name.i = ReadLE4(data + iter); iter += 4; + scr.codeIdx = ReadLE4(data + iter); iter += 4; + scr.argC = ReadLE4(data + iter); iter += 4; + + std::tie(scr.type, scr.name.i) = env->getScriptTypeACS0(scr.name.i); + } + + // Read string table. + + // Read string count. + if(size - iter < 4) throw ReadError(); + stringV.alloc(ReadLE4(data + iter)); iter += 4; + + // Read strings. + if(size - iter < stringV.size() * 4) throw ReadError(); + for(String *&str : stringV) + { + str = readStringACS0(data, size, ReadLE4(data + iter)); iter += 4; + } + + // Read code. + readCodeACS0(data, size, false); + + loaded = true; + } + + // + // Module::readCodeACS0 + // + void Module::readCodeACS0(Byte const *data, std::size_t size, bool compressed) + { + TracerACS0 tracer{env, data, size, compressed}; + + // Trace code paths from this module. + tracer.trace(this); + + codeV.alloc(tracer.codeC); + jumpMapV.alloc(tracer.jumpMapC); + + tracer.translate(this); + } + + // + // Module::readStringACS0 + // + String *Module::readStringACS0(Byte const *data, std::size_t size, std::size_t iter) + { + Byte const *begin, *end; + std::size_t len; + std::tie(begin, end, len) = ScanStringACS0(data, size, iter); + + // If result length is same as input length, no processing is needed. + if(static_cast<std::size_t>(end - begin) == len) + { + // Byte is always unsigned char, which is allowed to alias with char. + return env->getString( + reinterpret_cast<char const *>(begin), + reinterpret_cast<char const *>(end)); + } + else + return env->getString(ParseStringACS0(begin, end, len).get(), len); + } + + // + // Module::ParseStringACS0 + // + std::unique_ptr<char[]> Module::ParseStringACS0(Byte const *first, + Byte const *last, std::size_t len) + { + std::unique_ptr<char[]> buf{new char[len + 1]}; + char *bufItr = buf.get(); + + for(Byte const *s = first; s != last;) + { + if(*s == '\\') + { + if(++s == last) + break; + + switch(*s) + { + case 'a': *bufItr++ += '\a'; ++s; break; + case 'b': *bufItr++ += '\b'; ++s; break; + case 'c': *bufItr++ += '\x1C'; ++s; break; // ZDoom color escape + case 'f': *bufItr++ += '\f'; ++s; break; + case 'r': *bufItr++ += '\r'; ++s; break; + case 'n': *bufItr++ += '\n'; ++s; break; + case 't': *bufItr++ += '\t'; ++s; break; + case 'v': *bufItr++ += '\v'; ++s; break; + + case '0': case '1': case '2': case '3': + case '4': case '5': case '6': case '7': + for(unsigned int i = 3, c = 0; i-- && s != last; ++s) + { + switch(*s) + { + case '0': c = c * 8 + 00; continue; + case '1': c = c * 8 + 01; continue; + case '2': c = c * 8 + 02; continue; + case '3': c = c * 8 + 03; continue; + case '4': c = c * 8 + 04; continue; + case '5': c = c * 8 + 05; continue; + case '6': c = c * 8 + 06; continue; + case '7': c = c * 8 + 07; continue; + } + + *bufItr++ = c; + break; + } + break; + + case 'X': case 'x': + ++s; + for(unsigned int i = 2, c = 0; i-- && s != last; ++s) + { + switch(*s) + { + case '0': c = c * 16 + 0x0; continue; + case '1': c = c * 16 + 0x1; continue; + case '2': c = c * 16 + 0x2; continue; + case '3': c = c * 16 + 0x3; continue; + case '4': c = c * 16 + 0x4; continue; + case '5': c = c * 16 + 0x5; continue; + case '6': c = c * 16 + 0x6; continue; + case '7': c = c * 16 + 0x7; continue; + case '8': c = c * 16 + 0x8; continue; + case '9': c = c * 16 + 0x9; continue; + case 'A': c = c * 16 + 0xA; continue; + case 'B': c = c * 16 + 0xB; continue; + case 'C': c = c * 16 + 0xC; continue; + case 'D': c = c * 16 + 0xD; continue; + case 'E': c = c * 16 + 0xE; continue; + case 'F': c = c * 16 + 0xF; continue; + case 'a': c = c * 16 + 0xa; continue; + case 'b': c = c * 16 + 0xb; continue; + case 'c': c = c * 16 + 0xc; continue; + case 'd': c = c * 16 + 0xd; continue; + case 'e': c = c * 16 + 0xe; continue; + case 'f': c = c * 16 + 0xf; continue; + } + + *bufItr++ = c; + break; + } + break; + + default: + *bufItr++ = *s++; + break; + } + } + else + *bufItr++ = *s++; + } + + *bufItr++ = '\0'; + + return buf; + } + + // + // Module::ScanStringACS0 + // + std::tuple< + Byte const * /*begin*/, + Byte const * /*end*/, + std::size_t /*len*/> + Module::ScanStringACS0(Byte const *data, std::size_t size, std::size_t iter) + { + if(iter > size) throw ReadError(); + + Byte const *begin = data + iter; + Byte const *end = data + size; + Byte const *s = begin; + std::size_t len = 0; + + while(s != end && *s) + { + if(*s++ == '\\') + { + if(s == end || !*s) + break; + + switch(*s++) + { + case '0': case '1': case '2': case '3': + case '4': case '5': case '6': case '7': + for(int i = 2; i-- && s != end; ++s) + { + switch(*s) + { + case '0': case '1': case '2': case '3': + case '4': case '5': case '6': case '7': + continue; + } + + break; + } + break; + + case 'X': case 'x': + for(int i = 2; i-- && s != end; ++s) + { + switch(*s) + { + case '0': case '1': case '2': case '3': case '4': + case '5': case '6': case '7': case '8': case '9': + case 'A': case 'B': case 'C': case 'D': case 'E': case 'F': + case 'a': case 'b': case 'c': case 'd': case 'e': case 'f': + continue; + } + + break; + } + break; + + default: + break; + } + } + + ++len; + } + + // If not terminated by a null, string is malformed. + if(s == end) throw ReadError(); + + return std::make_tuple(begin, s, len); + } +} + +// EOF + diff --git a/src/acs/vm/ACSVM/ModuleACSE.cpp b/src/acs/vm/ACSVM/ModuleACSE.cpp new file mode 100644 index 0000000000000000000000000000000000000000..a38a29f5be3f26aa8652f1d6fdfedd7c997883e1 --- /dev/null +++ b/src/acs/vm/ACSVM/ModuleACSE.cpp @@ -0,0 +1,860 @@ +//----------------------------------------------------------------------------- +// +// Copyright (C) 2015-2017 David Hill +// +// See COPYING for license information. +// +//----------------------------------------------------------------------------- +// +// Module class bytecode reading. +// +//----------------------------------------------------------------------------- + +#include "Module.hpp" + +#include "Array.hpp" +#include "BinaryIO.hpp" +#include "Environment.hpp" +#include "Error.hpp" +#include "Function.hpp" +#include "Init.hpp" +#include "Jump.hpp" +#include "Script.hpp" + +#include <algorithm> + + +//----------------------------------------------------------------------------| +// Extern Functions | +// + +namespace ACSVM +{ + // + // Module::chunkIterACSE + // + bool Module::chunkIterACSE(Byte const *data, std::size_t size, + bool (Module::*chunker)(Byte const *, std::size_t, Word)) + { + std::size_t iter = 0; + + while(iter != size) + { + // Need space for header. + if(size - iter < 8) throw ReadError(); + + // Read header. + Word chunkName = ReadLE4(data + iter + 0); + Word chunkSize = ReadLE4(data + iter + 4); + + // Consume header. + iter += 8; + + // Need space for payload. + if(size - iter < chunkSize) throw ReadError(); + + // Read payload. + if((this->*chunker)(data + iter, chunkSize, chunkName)) + return true; + + // Consume payload. + iter += chunkSize; + } + + return false; + } + + // + // Module::chunkStrTabACSE + // + void Module::chunkStrTabACSE(Vector<String *> &strV, + Byte const *data, std::size_t size, bool junk) + { + std::size_t iter = 0; + + if(junk) + { + if(size < 12) throw ReadError(); + + /*junk = ReadLE4(data + iter);*/ iter += 4; + strV.alloc(ReadLE4(data + iter)); iter += 4; + /*junk = ReadLE4(data + iter);*/ iter += 4; + } + else + { + if(size < 4) throw ReadError(); + + strV.alloc(ReadLE4(data + iter)); iter += 4; + } + + if(size - iter < strV.size() * 4) throw ReadError(); + for(String *&str : strV) + { + str = readStringACS0(data, size, ReadLE4(data + iter)); iter += 4; + } + } + + // + // Module::chunkerACSE_AIMP + // + bool Module::chunkerACSE_AIMP(Byte const *data, std::size_t size, Word chunkName) + { + if(chunkName != MakeID("AIMP")) return false; + + if(size < 4) throw ReadError(); + + // Chunk starts with a number of entries. However, that is redundant with + // just checking for the end of the chunk as in MIMP, so do that. + + // Determine highest index. + Word arrC = 0; + for(std::size_t iter = 4; iter != size;) + { + if(size - iter < 8) throw ReadError(); + + Word idx = ReadLE4(data + iter); iter += 4; + /* len = LeadLE4(data + iter);*/ iter += 4; + + arrC = std::max<Word>(arrC, idx + 1); + + Byte const *next; + std::tie(std::ignore, next, std::ignore) = ScanStringACS0(data, size, iter); + + iter = next - data + 1; + } + + // Read imports. + arrImpV.alloc(arrC); + for(std::size_t iter = 4; iter != size;) + { + Word idx = ReadLE4(data + iter); iter += 4; + /* len = LeadLE4(data + iter);*/ iter += 4; + + Byte const *next; + std::size_t len; + std::tie(std::ignore, next, len) = ScanStringACS0(data, size, iter); + + std::unique_ptr<char[]> str = ParseStringACS0(data + iter, next, len); + + arrImpV[idx] = env->getString(str.get(), len); + + iter = next - data + 1; + } + + return true; + } + + // + // Module::chunkerACSE_AINI + // + bool Module::chunkerACSE_AINI(Byte const *data, std::size_t size, Word chunkName) + { + if(chunkName != MakeID("AINI")) return false; + + if(size < 4 || size % 4) throw ReadError(); + + Word idx = ReadLE4(data); + + // Silently ignore out of bounds initializers. + if(idx >= arrInitV.size()) return false; + + auto &init = arrInitV[idx]; + for(std::size_t iter = 4; iter != size; iter += 4) + init.setVal(iter / 4 - 1, ReadLE4(data + iter)); + + return false; + } + + // + // Module::chunkerACSE_ARAY + // + bool Module::chunkerACSE_ARAY(Byte const *data, std::size_t size, Word chunkName) + { + if(chunkName != MakeID("ARAY")) return false; + + if(size % 8) throw ReadError(); + + Word arrC = 0; + + // Determine highest index. + for(std::size_t iter = 0; iter != size; iter += 8) + arrC = std::max<Word>(arrC, ReadLE4(data + iter) + 1); + + arrNameV.alloc(arrC); + arrInitV.alloc(arrC); + arrSizeV.alloc(arrC); + + for(std::size_t iter = 0; iter != size;) + { + Word idx = ReadLE4(data + iter); iter += 4; + Word len = ReadLE4(data + iter); iter += 4; + + arrInitV[idx].reserve(len); + arrSizeV[idx] = len; + + // Use names from MEXP. + if(idx < regNameV.size()) + { + arrNameV[idx] = regNameV[idx]; + regNameV[idx] = nullptr; + } + } + + return true; + } + + // + // Module::chunkerACSE_ASTR + // + bool Module::chunkerACSE_ASTR(Byte const *data, std::size_t size, Word chunkName) + { + if(chunkName != MakeID("ASTR")) return false; + + if(size % 4) throw ReadError(); + + for(std::size_t iter = 0; iter != size;) + { + Word idx = ReadLE4(data + iter); iter += 4; + + // Silently ignore out of bounds initializers. + if(idx >= arrInitV.size()) continue; + + auto &init = arrInitV[idx]; + for(Word i = 0, e = arrSizeV[idx]; i != e; ++i) + init.setTag(i, InitTag::String); + } + + return false; + } + + // + // Module::chunkerACSE_ATAG + // + bool Module::chunkerACSE_ATAG(Byte const *data, std::size_t size, Word chunkName) + { + if(chunkName != MakeID("ATAG")) return false; + + if(size < 5 || data[0]) throw ReadError(); + + Word idx = ReadLE4(data + 1); + + // Silently ignore out of bounds initializers. + if(idx >= arrInitV.size()) return false; + + auto &init = arrInitV[idx]; + for(std::size_t iter = 5; iter != size; ++iter) + { + switch(data[iter]) + { + case 0: init.setTag(iter - 5, InitTag::Integer); break; + case 1: init.setTag(iter - 5, InitTag::String); break; + case 2: init.setTag(iter - 5, InitTag::Function); break; + } + } + + return false; + } + + // + // Module::chunkerACSE_FARY + // + bool Module::chunkerACSE_FARY(Byte const *data, std::size_t size, Word chunkName) + { + if(chunkName != MakeID("FARY")) return false; + + if(size < 2 || (size - 2) % 4) throw ReadError(); + + Word idx = ReadLE2(data); + std::size_t arrC = (size - 2) / 4; + + if(idx < functionV.size() && functionV[idx]) + functionV[idx]->locArrC = arrC; + + return false; + } + + // + // Module::chunkerACSE_FNAM + // + bool Module::chunkerACSE_FNAM(Byte const *data, std::size_t size, Word chunkName) + { + if(chunkName != MakeID("FNAM")) return false; + + chunkStrTabACSE(funcNameV, data, size, false); + + return true; + } + + // + // Module::chunkerACSE_FUNC + // + bool Module::chunkerACSE_FUNC(Byte const *data, std::size_t size, Word chunkName) + { + if(chunkName != MakeID("FUNC")) return false; + + if(size % 8) throw ReadError(); + + // Read functions. + functionV.alloc(size / 8); + + std::size_t iter = 0; + for(Function *&func : functionV) + { + Word idx = iter / 8; + Word argC = ReadLE1(data + iter); iter += 1; + Word locRegC = ReadLE1(data + iter); iter += 1; + Word flags = ReadLE2(data + iter); iter += 2; + Word codeIdx = ReadLE4(data + iter); iter += 4; + + // Ignore undefined functions for now. + if(!codeIdx) continue; + + String *funcName = idx < funcNameV.size() ? funcNameV[idx] : nullptr; + Function *function = env->getFunction(this, funcName); + + function->argC = argC; + function->locRegC = locRegC; + function->flagRet = flags & 0x0001; + function->codeIdx = codeIdx; + + func = function; + } + + return true; + } + + // + // Module::chunkerACSE_JUMP + // + bool Module::chunkerACSE_JUMP(Byte const *data, std::size_t size, Word chunkName) + { + if(chunkName != MakeID("JUMP")) return false; + + if(size % 4) throw ReadError(); + + // Read jumps. + jumpV.alloc(size / 4); + + std::size_t iter = 0; + for(Jump &jump : jumpV) + { + jump.codeIdx = ReadLE4(data + iter); iter += 4; + } + + return true; + } + + // + // Module::chunkerACSE_LOAD + // + bool Module::chunkerACSE_LOAD(Byte const *data, std::size_t size, Word chunkName) + { + if(chunkName != MakeID("LOAD")) return false; + + // Count imports. + std::size_t importC = 0; + for(Byte const *iter = data, *end = data + size; iter != end; ++ iter) + if(!*iter) ++importC; + + importV.alloc(importC); + + for(std::size_t iter = 0, i = 0; iter != size;) + { + Byte const *next; + std::size_t len; + std::tie(std::ignore, next, len) = ScanStringACS0(data, size, iter); + + std::unique_ptr<char[]> str = ParseStringACS0(data + iter, next, len); + + auto loadName = env->getModuleName(str.get(), len); + if(loadName != name) + importV[i++] = env->getModule(std::move(loadName)); + else + importV[i++] = this; + + iter = next - data + 1; + } + + return true; + } + + // + // Module::chunkerACSE_MEXP + // + bool Module::chunkerACSE_MEXP(Byte const *data, std::size_t size, Word chunkName) + { + if(chunkName != MakeID("MEXP")) return false; + + chunkStrTabACSE(regNameV, data, size, false); + + return true; + } + + // + // Module::chunkerACSE_MIMP + // + bool Module::chunkerACSE_MIMP(Byte const *data, std::size_t size, Word chunkName) + { + if(chunkName != MakeID("MIMP")) return false; + + // Determine highest index. + Word regC = 0; + for(std::size_t iter = 0; iter != size;) + { + if(size - iter < 4) throw ReadError(); + + Word idx = ReadLE4(data + iter); iter += 4; + + regC = std::max<Word>(regC, idx + 1); + + Byte const *next; + std::tie(std::ignore, next, std::ignore) = ScanStringACS0(data, size, iter); + + iter = next - data + 1; + } + + // Read imports. + regImpV.alloc(regC); + for(std::size_t iter = 0; iter != size;) + { + Word idx = ReadLE4(data + iter); iter += 4; + + Byte const *next; + std::size_t len; + std::tie(std::ignore, next, len) = ScanStringACS0(data, size, iter); + + std::unique_ptr<char[]> str = ParseStringACS0(data + iter, next, len); + + regImpV[idx] = env->getString(str.get(), len); + + iter = next - data + 1; + } + + return true; + } + + // + // Module::chunkerACSE_MINI + // + bool Module::chunkerACSE_MINI(Byte const *data, std::size_t size, Word chunkName) + { + if(chunkName != MakeID("MINI")) return false; + + if(size % 4 || size < 4) throw ReadError("bad MINI size"); + + Word idx = ReadLE4(data); + Word regC = idx + size / 4 - 1; + + if(regC > regInitV.size()) + regInitV.realloc(regC); + + for(std::size_t iter = 4; iter != size; iter += 4) + regInitV[idx++] = ReadLE4(data + iter); + + return true; + } + + // + // Module::chunkerACSE_MSTR + // + bool Module::chunkerACSE_MSTR(Byte const *data, std::size_t size, Word chunkName) + { + if(chunkName != MakeID("MSTR")) return false; + + if(size % 4) throw ReadError(); + + for(std::size_t iter = 0; iter != size;) + { + Word idx = ReadLE4(data + iter); iter += 4; + + // Silently ignore out of bounds initializers. + if(idx < regInitV.size()) + regInitV[idx].tag = InitTag::String; + } + + return false; + } + + // + // Module::chunkerACSE_SARY + // + bool Module::chunkerACSE_SARY(Byte const *data, std::size_t size, Word chunkName) + { + if(chunkName != MakeID("SARY")) return false; + + if(size < 2 || (size - 2) % 4) throw ReadError(); + + Word nameInt = ReadLE2(data); + std::size_t arrC = (size - 2) / 4; + + if(nameInt & 0x8000) nameInt |= 0xFFFF0000; + + for(Script &scr : scriptV) + if(scr.name.i == nameInt) scr.locArrC = arrC; + + return false; + } + + // + // Module::chunkerACSE_SFLG + // + bool Module::chunkerACSE_SFLG(Byte const *data, std::size_t size, Word chunkName) + { + if(chunkName != MakeID("SFLG")) return false; + + if(size % 4) throw ReadError(); + + for(std::size_t iter = 0; iter != size;) + { + Word nameInt = ReadLE2(data + iter); iter += 2; + Word flags = ReadLE2(data + iter); iter += 2; + + bool flagNet = !!(flags & 0x0001); + bool flagClient = !!(flags & 0x0002); + + if(nameInt & 0x8000) nameInt |= 0xFFFF0000; + + for(Script &scr : scriptV) + { + if(scr.name.i == nameInt) + { + scr.flagClient = flagClient; + scr.flagNet = flagNet; + } + } + } + + return false; + } + + // + // Module::chunkerACSE_SNAM + // + bool Module::chunkerACSE_SNAM(Byte const *data, std::size_t size, Word chunkName) + { + if(chunkName != MakeID("SNAM")) return false; + + chunkStrTabACSE(scrNameV, data, size, false); + + return true; + } + + // + // Module::chunkerACSE_SPTR8 + // + // Reads 8-byte SPTR chunk. + // + bool Module::chunkerACSE_SPTR8(Byte const *data, std::size_t size, Word chunkName) + { + if(chunkName != MakeID("SPTR")) return false; + + if(size % 8) throw ReadError(); + + // Read scripts. + scriptV.alloc(size / 8, this); + + std::size_t iter = 0; + for(Script &scr : scriptV) + { + Word nameInt = ReadLE2(data + iter); iter += 2; + Word type = ReadLE1(data + iter); iter += 1; + scr.argC = ReadLE1(data + iter); iter += 1; + scr.codeIdx = ReadLE4(data + iter); iter += 4; + + if(nameInt & 0x8000) nameInt |= 0xFFFF0000; + setScriptNameTypeACSE(&scr, nameInt, type); + } + + return true; + } + + // + // Module::chunkerACSE_SPTR12 + // + // Reads 12-byte SPTR chunk. + // + bool Module::chunkerACSE_SPTR12(Byte const *data, std::size_t size, Word chunkName) + { + if(chunkName != MakeID("SPTR")) return false; + + if(size % 12) throw ReadError(); + + // Read scripts. + scriptV.alloc(size / 12, this); + + std::size_t iter = 0; + for(Script &scr : scriptV) + { + Word nameInt = ReadLE2(data + iter); iter += 2; + Word type = ReadLE2(data + iter); iter += 2; + scr.codeIdx = ReadLE4(data + iter); iter += 4; + scr.argC = ReadLE4(data + iter); iter += 4; + + if(nameInt & 0x8000) nameInt |= 0xFFFF0000; + setScriptNameTypeACSE(&scr, nameInt, type); + } + + return true; + } + + // + // Module::chunkerACSE_STRE + // + bool Module::chunkerACSE_STRE(Byte const *data, std::size_t size, Word chunkName) + { + if(chunkName != MakeID("STRE")) return false; + + std::size_t iter = 0; + + if(size < 12) throw ReadError(); + + /*junk = ReadLE4(data + iter);*/ iter += 4; + stringV.alloc(ReadLE4(data + iter)); iter += 4; + /*junk = ReadLE4(data + iter);*/ iter += 4; + + if(size - iter < stringV.size() * 4) throw ReadError(); + for(String *&str : stringV) + { + std::size_t offset = ReadLE4(data + iter); iter += 4; + + // Decrypt string. + std::unique_ptr<Byte[]> buf; + std::size_t len; + std::tie(buf, len) = DecryptStringACSE(data, size, offset); + + // Scan string. + Byte const *bufEnd; + std::tie(std::ignore, bufEnd, len) = ScanStringACS0(buf.get(), len, 0); + + // Parse string. + str = env->getString(ParseStringACS0(buf.get(), bufEnd, len).get(), len); + } + + return true; + } + + // + // Module::chunkerACSE_STRL + // + bool Module::chunkerACSE_STRL(Byte const *data, std::size_t size, Word chunkName) + { + if(chunkName != MakeID("STRL")) return false; + + chunkStrTabACSE(stringV, data, size, true); + + return true; + } + + // + // Module::chunkerACSE_SVCT + // + bool Module::chunkerACSE_SVCT(Byte const *data, std::size_t size, Word chunkName) + { + if(chunkName != MakeID("SVCT")) return false; + + if(size % 4) throw ReadError(); + + for(std::size_t iter = 0; iter != size;) + { + Word nameInt = ReadLE2(data + iter); iter += 2; + Word regC = ReadLE2(data + iter); iter += 2; + + if(nameInt & 0x8000) nameInt |= 0xFFFF0000; + + for(Script &scr : scriptV) + { + if(scr.name.i == nameInt) + scr.locRegC = regC; + } + } + + return false; + } + + // + // Module::readBytecodeACSE + // + void Module::readBytecodeACSE(Byte const *data, std::size_t size, + bool compressed, std::size_t offset) + { + std::size_t iter = offset; + + // Find table start. + if(iter > size || size - iter < 4) throw ReadError(); + iter = ReadLE4(data + iter); + if(iter > size) throw ReadError(); + + // Read chunks. + if(offset == 4) + { + readChunksACSE(data + iter, size - iter, false); + } + else + { + if(iter <= offset) + readChunksACSE(data + iter, offset - iter, true); + else + readChunksACSE(data + iter, size - iter, true); + } + + // Read code. + readCodeACS0(data, size, compressed); + + loaded = true; + } + + // + // Module::readChunksACSE + // + void Module::readChunksACSE(Byte const *data, std::size_t size, bool fakeACS0) + { + // MEXP - Module Variable/Array Export + chunkIterACSE(data, size, &Module::chunkerACSE_MEXP); + + // ARAY - Module Arrays + chunkIterACSE(data, size, &Module::chunkerACSE_ARAY); + + // AINI - Module Array Init + chunkIterACSE(data, size, &Module::chunkerACSE_AINI); + + // FNAM - Function Names + chunkIterACSE(data, size, &Module::chunkerACSE_FNAM); + + // FUNC - Functions + chunkIterACSE(data, size, &Module::chunkerACSE_FUNC); + + // FARY - Function Arrays + chunkIterACSE(data, size, &Module::chunkerACSE_FARY); + + // JUMP - Dynamic Jump Targets + chunkIterACSE(data, size, &Module::chunkerACSE_JUMP); + + // MINI - Module Variable Init + chunkIterACSE(data, size, &Module::chunkerACSE_MINI); + + // SNAM - Script Names + chunkIterACSE(data, size, &Module::chunkerACSE_SNAM); + + // SPTR - Script Pointers + if(fakeACS0) + chunkIterACSE(data, size, &Module::chunkerACSE_SPTR8); + else + chunkIterACSE(data, size, &Module::chunkerACSE_SPTR12); + + // SARY - Script Arrays + chunkIterACSE(data, size, &Module::chunkerACSE_SARY); + + // SFLG - Script Flags + chunkIterACSE(data, size, &Module::chunkerACSE_SFLG); + + // SVCT - Script Variable Count + chunkIterACSE(data, size, &Module::chunkerACSE_SVCT); + + // STRE - Encrypted String Literals + if(!chunkIterACSE(data, size, &Module::chunkerACSE_STRE)) + { + // STRL - String Literals + chunkIterACSE(data, size, &Module::chunkerACSE_STRL); + } + + // LOAD - Library Loading + chunkIterACSE(data, size, &Module::chunkerACSE_LOAD); + + // Process function imports. + for(auto &func : functionV) + { + if(func) continue; + + std::size_t idx = &func - functionV.data(); + + if(idx >= funcNameV.size()) continue; + + auto &funcName = funcNameV[idx]; + + if(!funcName) continue; + + for(auto &import : importV) + { + for(auto &funcImp : import->functionV) + { + if(funcImp && funcImp->name == funcName) + { + func = funcImp; + goto func_found; + } + } + } + + func_found:; + } + + // AIMP - Module Array Import + chunkIterACSE(data, size, &Module::chunkerACSE_AIMP); + + // MIMP - Module Variable Import + chunkIterACSE(data, size, &Module::chunkerACSE_MIMP); + + // ASTR - Module Array Strings + chunkIterACSE(data, size, &Module::chunkerACSE_ASTR); + + // ATAG - Module Array Tagging + chunkIterACSE(data, size, &Module::chunkerACSE_ATAG); + + // MSTR - Module Variable Strings + chunkIterACSE(data, size, &Module::chunkerACSE_MSTR); + + for(auto &init : arrInitV) + init.finish(); + } + + // + // Module::setScriptNameTypeACSE + // + void Module::setScriptNameTypeACSE(Script *scr, Word nameInt, Word type) + { + // If high bit is set, script is named. + if((scr->name.i = nameInt) & 0x80000000) + { + // Fetch name. + Word nameIdx = ~scr->name.i; + if(nameIdx < scrNameV.size()) + scr->name.s = scrNameV[nameIdx]; + } + + scr->type = env->getScriptTypeACSE(type); + } + + // + // Module::DecryptStringACSE + // + std::pair< + std::unique_ptr<Byte[]> /*data*/, + std::size_t /*size*/> + Module::DecryptStringACSE(Byte const *data, std::size_t size, std::size_t iter) + { + Word const key = iter * 157135; + + // Calculate length. Start at 1 for null terminator. + std::size_t len = 1; + for(std::size_t i = iter, n = 0;; ++i, ++n, ++len) + { + if(i == size) throw ReadError(); + + Byte c = static_cast<Byte>(data[i] ^ (n / 2 + key)); + if(!c) break; + } + + // Decrypt data. + std::unique_ptr<Byte[]> buf{new Byte[len]}; + for(std::size_t i = iter, n = 0;; ++i, ++n) + { + Byte c = static_cast<Byte>(data[i] ^ (n / 2 + key)); + if(!(buf[n] = c)) break; + } + + return {std::move(buf), len}; + } +} + +// EOF + diff --git a/src/acs/vm/ACSVM/PrintBuf.cpp b/src/acs/vm/ACSVM/PrintBuf.cpp new file mode 100644 index 0000000000000000000000000000000000000000..928585433cfc8bc92bca357cc8e6c2cd89bd7de5 --- /dev/null +++ b/src/acs/vm/ACSVM/PrintBuf.cpp @@ -0,0 +1,148 @@ +//----------------------------------------------------------------------------- +// +// Copyright (C) 2015 David Hill +// +// See COPYING for license information. +// +//----------------------------------------------------------------------------- +// +// PrintBuf class. +// +//----------------------------------------------------------------------------- + +#include "PrintBuf.hpp" + +#include "BinaryIO.hpp" + +#include <cstdio> +#include <cstdlib> +#include <new> + + +//----------------------------------------------------------------------------| +// Extern Functions | +// + +namespace ACSVM +{ + // + // PrintBuf constructor + // + PrintBuf::PrintBuf() : + buffer{nullptr}, + bufEnd{nullptr}, + bufBeg{nullptr}, + bufPtr{nullptr} + { + } + + // + // PrintBuf destructor + // + PrintBuf::~PrintBuf() + { + std::free(buffer); + } + + // + // PrintBuf::drop + // + void PrintBuf::drop() + { + if(bufBeg != buffer) + { + bufPtr = bufBeg - 4; + bufBeg = bufPtr - ReadLE4(reinterpret_cast<Byte *>(bufPtr)); + } + else + bufPtr = bufBeg; + } + + // + // PrintBuf::format + // + void PrintBuf::format(char const *fmt, ...) + { + va_list arg; + va_start(arg, fmt); + formatv(fmt, arg); + va_end(arg); + } + + // + // PrintBuf::formatv + // + void PrintBuf::formatv(char const *fmt, va_list arg) + { + bufPtr += std::vsprintf(bufPtr, fmt, arg); + } + + // + // PrintBuf::getLoadBuf + // + char *PrintBuf::getLoadBuf(std::size_t countFull, std::size_t count) + { + if(static_cast<std::size_t>(bufEnd - buffer) <= countFull) + { + char *bufNew; + if(!(bufNew = static_cast<char *>(std::realloc(buffer, countFull + 1)))) + throw std::bad_alloc(); + + buffer = bufNew; + bufEnd = buffer + countFull + 1; + } + + bufPtr = buffer + countFull; + bufBeg = bufPtr - count; + + return buffer; + } + + // + // PrintBuf::push + // + void PrintBuf::push() + { + reserve(4); + WriteLE4(reinterpret_cast<Byte *>(bufPtr), bufPtr - bufBeg); + bufBeg = bufPtr += 4; + } + + // + // PrintBuf::reserve + // + void PrintBuf::reserve(std::size_t count) + { + if(static_cast<std::size_t>(bufEnd - bufPtr) > count) + return; + + // Allocate extra to anticipate further reserves. +1 for null. + count = count * 2 + 1; + + std::size_t idxEnd = bufEnd - buffer; + std::size_t idxBeg = bufBeg - buffer; + std::size_t idxPtr = bufPtr - buffer; + + // Check for size overflow. + if(SIZE_MAX - idxEnd < count) + throw std::bad_alloc(); + + // Check that the current segment won't pass the push limit. + if(UINT32_MAX - (idxEnd - idxBeg) < count) + throw std::bad_alloc(); + + idxEnd += count; + + char *bufNew; + if(!(bufNew = static_cast<char *>(std::realloc(buffer, idxEnd)))) + throw std::bad_alloc(); + + buffer = bufNew; + bufEnd = buffer + idxEnd; + bufBeg = buffer + idxBeg; + bufPtr = buffer + idxPtr; + } +} + +// EOF + diff --git a/src/acs/vm/ACSVM/PrintBuf.hpp b/src/acs/vm/ACSVM/PrintBuf.hpp new file mode 100644 index 0000000000000000000000000000000000000000..8cbf64d4828e6f8613b6093123c070f88a14be34 --- /dev/null +++ b/src/acs/vm/ACSVM/PrintBuf.hpp @@ -0,0 +1,74 @@ +//----------------------------------------------------------------------------- +// +// Copyright (C) 2015 David Hill +// +// See COPYING for license information. +// +//----------------------------------------------------------------------------- +// +// PrintBuf class. +// +//----------------------------------------------------------------------------- + +#ifndef ACSVM__PrintBuf_H__ +#define ACSVM__PrintBuf_H__ + +#include "Types.hpp" + +#include <cstdarg> + + +//----------------------------------------------------------------------------| +// Types | +// + +namespace ACSVM +{ + // + // PrintBuf + // + class PrintBuf + { + public: + PrintBuf(); + ~PrintBuf(); + + void clear() {bufBeg = bufPtr = buffer;} + + char const *data() const {return *bufPtr = '\0', bufBeg;} + char const *dataFull() const {return buffer;} + + void drop(); + + // Formats using sprintf. Does not reserve space. + void format(char const *fmt, ...); + void formatv(char const *fmt, std::va_list arg); + + // Returns a pointer to count chars to write into. The caller must write + // to the entire returned buffer. Does not reserve space. + char *getBuf(std::size_t count) + {char *s = bufPtr; bufPtr += count; return s;} + + // Prepares the buffer to be deserialized. + char *getLoadBuf(std::size_t countFull, std::size_t count); + + void push(); + + // Writes literal characters. Does not reserve space. + void put(char c) {*bufPtr++ = c;} + void put(char const *s) {while(*s) *bufPtr++ = *s++;} + void put(char const *s, std::size_t n) {while(n--) *bufPtr++ = *s++;} + + // Ensures at least count chars are available for writing into. + void reserve(std::size_t count); + + std::size_t size() const {return bufPtr - bufBeg;} + std::size_t sizeFull() const {return bufPtr - buffer;} + + private: + char *buffer, *bufEnd, *bufBeg, *bufPtr; + }; +} + +#endif//ACSVM__PrintBuf_H__ + diff --git a/src/acs/vm/ACSVM/Scope.cpp b/src/acs/vm/ACSVM/Scope.cpp new file mode 100644 index 0000000000000000000000000000000000000000..7f56a7e6fc57528eac81357b31513e834b7cf456 --- /dev/null +++ b/src/acs/vm/ACSVM/Scope.cpp @@ -0,0 +1,1299 @@ +//----------------------------------------------------------------------------- +// +// Copyright (C) 2015-2020 David Hill +// +// See COPYING for license information. +// +//----------------------------------------------------------------------------- +// +// Scope classes. +// +//----------------------------------------------------------------------------- + +#include "Scope.hpp" + +#include "Action.hpp" +#include "BinaryIO.hpp" +#include "Environment.hpp" +#include "HashMap.hpp" +#include "HashMapFixed.hpp" +#include "Init.hpp" +#include "Module.hpp" +#include "Script.hpp" +#include "Serial.hpp" +#include "Thread.hpp" + +#include <algorithm> +#include <unordered_set> +#include <vector> + + +//----------------------------------------------------------------------------| +// Types | +// + +namespace ACSVM +{ + // + // GlobalScope::PrivData + // + struct GlobalScope::PrivData + { + HashMapKeyMem<Word, HubScope, &HubScope::id, &HubScope::hashLink> scopes; + }; + + // + // HubScope::PrivData + // + struct HubScope::PrivData + { + HashMapKeyMem<Word, MapScope, &MapScope::id, &MapScope::hashLink> scopes; + }; + + // + // MapScope::PrivData + // + struct MapScope::PrivData + { + HashMapFixed<Module *, ModuleScope> scopes; + + HashMapFixed<Word, Script *> scriptInt; + HashMapFixed<String *, Script *> scriptStr; + + HashMapFixed<Script *, Thread *> scriptThread; + }; +} + + +//----------------------------------------------------------------------------| +// Extern Objects | +// + +namespace ACSVM +{ + constexpr std::size_t ModuleScope::ArrC; + constexpr std::size_t ModuleScope::RegC; +} + + +//----------------------------------------------------------------------------| +// Extern Functions | +// + +namespace ACSVM +{ + // + // GlobalScope constructor + // + GlobalScope::GlobalScope(Environment *env_, Word id_) : + env{env_}, + id {id_}, + + arrV{}, + regV{}, + + hashLink{this}, + + active{false}, + + pd{new PrivData} + { + } + + // + // GlobalScope destructor + // + GlobalScope::~GlobalScope() + { + reset(); + delete pd; + } + + // + // GlobalScope::countActiveThread + // + std::size_t GlobalScope::countActiveThread() const + { + std::size_t n = 0; + + for(auto &scope : pd->scopes) + { + if(scope.active) + n += scope.countActiveThread(); + } + + return n; + } + + // + // GlobalScope::exec + // + void GlobalScope::exec() + { + // Delegate deferred script actions. + for(auto itr = scriptAction.begin(), end = scriptAction.end(); itr != end;) + { + auto scope = pd->scopes.find(itr->id.global); + if(scope && scope->active) + itr++->link.relink(&scope->scriptAction); + else + ++itr; + } + + for(auto &scope : pd->scopes) + { + if(scope.active) + scope.exec(); + } + } + + // + // GlobalScope::freeHubScope + // + void GlobalScope::freeHubScope(HubScope *scope) + { + pd->scopes.unlink(scope); + delete scope; + } + + // + // GlobalScope::getHubScope + // + HubScope *GlobalScope::getHubScope(Word scopeID) + { + if(auto *scope = pd->scopes.find(scopeID)) + return scope; + + auto scope = new HubScope(this, scopeID); + pd->scopes.insert(scope); + return scope; + } + + // + // GlobalScope::hasActiveThread + // + bool GlobalScope::hasActiveThread() const + { + for(auto &scope : pd->scopes) + { + if(scope.active && scope.hasActiveThread()) + return true; + } + + return false; + } + + // + // GlobalScope::loadState + // + void GlobalScope::loadState(Serial &in) + { + reset(); + + in.readSign(Signature::GlobalScope); + + for(auto &arr : arrV) + arr.loadState(in); + + for(auto ® : regV) + reg = ReadVLN<Word>(in); + + env->readScriptActions(in, scriptAction); + + active = in.in->get() != '\0'; + + for(auto n = ReadVLN<std::size_t>(in); n--;) + getHubScope(ReadVLN<Word>(in))->loadState(in); + + in.readSign(~Signature::GlobalScope); + } + + // + // GlobalScope::lockStrings + // + void GlobalScope::lockStrings() const + { + for(auto &arr : arrV) arr.lockStrings(env); + for(auto ® : regV) ++env->getString(reg)->lock; + + for(auto &action : scriptAction) + action.lockStrings(env); + + for(auto &scope : pd->scopes) + scope.lockStrings(); + } + + // + // GlobalScope::refStrings + // + void GlobalScope::refStrings() const + { + for(auto &arr : arrV) arr.refStrings(env); + for(auto ® : regV) env->getString(reg)->ref = true; + + for(auto &action : scriptAction) + action.refStrings(env); + + for(auto &scope : pd->scopes) + scope.refStrings(); + } + + // + // GlobalScope::reset + // + void GlobalScope::reset() + { + while(scriptAction.next->obj) + delete scriptAction.next->obj; + + pd->scopes.free(); + } + + // + // GlobalScope::saveState + // + void GlobalScope::saveState(Serial &out) const + { + out.writeSign(Signature::GlobalScope); + + for(auto &arr : arrV) + arr.saveState(out); + + for(auto ® : regV) + WriteVLN(out, reg); + + env->writeScriptActions(out, scriptAction); + + out.out->put(active ? '\1' : '\0'); + + WriteVLN(out, pd->scopes.size()); + for(auto &scope : pd->scopes) + { + WriteVLN(out, scope.id); + scope.saveState(out); + } + + out.writeSign(~Signature::GlobalScope); + } + + // + // GlobalScope::unlockStrings + // + void GlobalScope::unlockStrings() const + { + for(auto &arr : arrV) arr.unlockStrings(env); + for(auto ® : regV) --env->getString(reg)->lock; + + for(auto &action : scriptAction) + action.unlockStrings(env); + + for(auto &scope : pd->scopes) + scope.unlockStrings(); + } + + // + // HubScope constructor + // + HubScope::HubScope(GlobalScope *global_, Word id_) : + env {global_->env}, + global{global_}, + id {id_}, + + arrV{}, + regV{}, + + hashLink{this}, + + active{false}, + + pd{new PrivData} + { + } + + // + // HubScope destructor + // + HubScope::~HubScope() + { + reset(); + delete pd; + } + + // + // HubScope::countActiveThread + // + std::size_t HubScope::countActiveThread() const + { + std::size_t n = 0; + + for(auto &scope : pd->scopes) + { + if(scope.active) + n += scope.countActiveThread(); + } + + return n; + } + + // + // HubScope::exec + // + void HubScope::exec() + { + // Delegate deferred script actions. + for(auto itr = scriptAction.begin(), end = scriptAction.end(); itr != end;) + { + auto scope = pd->scopes.find(itr->id.global); + if(scope && scope->active) + itr++->link.relink(&scope->scriptAction); + else + ++itr; + } + + for(auto &scope : pd->scopes) + { + if(scope.active) + scope.exec(); + } + } + + // + // HubScope::freeMapScope + // + void HubScope::freeMapScope(MapScope *scope) + { + pd->scopes.unlink(scope); + delete scope; + } + + // + // HubScope::getMapScope + // + MapScope *HubScope::getMapScope(Word scopeID) + { + if(auto *scope = pd->scopes.find(scopeID)) + return scope; + + auto scope = new MapScope(this, scopeID); + pd->scopes.insert(scope); + return scope; + } + + // + // HubScope::hasActiveThread + // + bool HubScope::hasActiveThread() const + { + for(auto &scope : pd->scopes) + { + if(scope.active && scope.hasActiveThread()) + return true; + } + + return false; + } + + // + // HubScope::loadState + // + void HubScope::loadState(Serial &in) + { + reset(); + + in.readSign(Signature::HubScope); + + for(auto &arr : arrV) + arr.loadState(in); + + for(auto ® : regV) + reg = ReadVLN<Word>(in); + + env->readScriptActions(in, scriptAction); + + active = in.in->get() != '\0'; + + for(auto n = ReadVLN<std::size_t>(in); n--;) + getMapScope(ReadVLN<Word>(in))->loadState(in); + + in.readSign(~Signature::HubScope); + } + + // + // HubScope::lockStrings + // + void HubScope::lockStrings() const + { + for(auto &arr : arrV) arr.lockStrings(env); + for(auto ® : regV) ++env->getString(reg)->lock; + + for(auto &action : scriptAction) + action.lockStrings(env); + + for(auto &scope : pd->scopes) + scope.lockStrings(); + } + + // + // HubScope::refStrings + // + void HubScope::refStrings() const + { + for(auto &arr : arrV) arr.refStrings(env); + for(auto ® : regV) env->getString(reg)->ref = true; + + for(auto &action : scriptAction) + action.refStrings(env); + + for(auto &scope : pd->scopes) + scope.refStrings(); + } + + // + // HubScope::reset + // + void HubScope::reset() + { + while(scriptAction.next->obj) + delete scriptAction.next->obj; + + pd->scopes.free(); + } + + // + // HubScope::saveState + // + void HubScope::saveState(Serial &out) const + { + out.writeSign(Signature::HubScope); + + for(auto &arr : arrV) + arr.saveState(out); + + for(auto ® : regV) + WriteVLN(out, reg); + + env->writeScriptActions(out, scriptAction); + + out.out->put(active ? '\1' : '\0'); + + WriteVLN(out, pd->scopes.size()); + for(auto &scope : pd->scopes) + { + WriteVLN(out, scope.id); + scope.saveState(out); + } + + out.writeSign(~Signature::HubScope); + } + + // + // HubScope::unlockStrings + // + void HubScope::unlockStrings() const + { + for(auto &arr : arrV) arr.unlockStrings(env); + for(auto ® : regV) --env->getString(reg)->lock; + + for(auto &action : scriptAction) + action.unlockStrings(env); + + for(auto &scope : pd->scopes) + scope.unlockStrings(); + } + + // + // MapScope constructor + // + MapScope::MapScope(HubScope *hub_, Word id_) : + env{hub_->env}, + hub{hub_}, + id {id_}, + + hashLink{this}, + + module0{nullptr}, + + active {false}, + clampCallSpec{false}, + + pd{new PrivData} + { + } + + // + // MapScope destructor + // + MapScope::~MapScope() + { + reset(); + delete pd; + } + + // + // MapScope::addModules + // + void MapScope::addModules(Module *const *moduleV, std::size_t moduleC) + { + module0 = moduleC ? moduleV[0] : nullptr; + + // Find all associated modules. + + struct + { + std::unordered_set<Module *> set; + std::vector<Module *> vec; + + void add(Module *module) + { + if(!set.insert(module).second) return; + + vec.push_back(module); + for(auto &import : module->importV) + add(import); + } + } modules; + + for(auto itr = moduleV, end = itr + moduleC; itr != end; ++itr) + modules.add(*itr); + + // Count scripts. + + std::size_t scriptThrC = 0; + std::size_t scriptIntC = 0; + std::size_t scriptStrC = 0; + + for(auto &module : modules.vec) + { + for(auto &script : module->scriptV) + { + ++scriptThrC; + if(script.name.s) + ++scriptStrC; + else + ++scriptIntC; + } + } + + // Create lookup tables. + + pd->scopes.alloc(modules.vec.size()); + pd->scriptInt.alloc(scriptIntC); + pd->scriptStr.alloc(scriptStrC); + pd->scriptThread.alloc(scriptThrC); + + auto scopeItr = pd->scopes.begin(); + auto scriptIntItr = pd->scriptInt.begin(); + auto scriptStrItr = pd->scriptStr.begin(); + auto scriptThrItr = pd->scriptThread.begin(); + + for(auto &module : modules.vec) + { + using ElemScope = HashMapFixed<Module *, ModuleScope>::Elem; + + new(scopeItr++) ElemScope{module, {this, module}, nullptr}; + + for(auto &script : module->scriptV) + { + using ElemInt = HashMapFixed<Word, Script *>::Elem; + using ElemStr = HashMapFixed<String *, Script *>::Elem; + using ElemThr = HashMapFixed<Script *, Thread *>::Elem; + + new(scriptThrItr++) ElemThr{&script, nullptr, nullptr}; + + if(script.name.s) + new(scriptStrItr++) ElemStr{script.name.s, &script, nullptr}; + else + new(scriptIntItr++) ElemInt{script.name.i, &script, nullptr}; + } + } + + pd->scopes.build(); + pd->scriptInt.build(); + pd->scriptStr.build(); + pd->scriptThread.build(); + + for(auto &scope : pd->scopes) + scope.val.import(); + } + + // + // MapScope::countActiveThread + // + std::size_t MapScope::countActiveThread() const + { + return threadActive.size(); + } + + // + // MapScope::exec + // + void MapScope::exec() + { + // Execute deferred script actions. + while(scriptAction.next->obj) + { + ScriptAction *action = scriptAction.next->obj; + Script *script = findScript(action->name); + + if(script) switch(action->action) + { + case ScriptAction::Start: + scriptStart(script, {action->argV.data(), action->argV.size()}); + break; + + case ScriptAction::StartForced: + scriptStartForced(script, {action->argV.data(), action->argV.size()}); + break; + + case ScriptAction::Stop: + scriptStop(script); + break; + + case ScriptAction::Pause: + scriptPause(script); + break; + } + + delete action; + } + + // Execute running threads. + for(auto itr = threadActive.begin(), end = threadActive.end(); itr != end;) + { + itr->exec(); + if(itr->state == ThreadState::Inactive) + freeThread(&*itr++); + else + ++itr; + } + } + + // + // MapScope::findScript + // + Script *MapScope::findScript(ScriptName name) + { + return name.s ? findScript(name.s) : findScript(name.i); + } + + // + // MapScope::findScript + // + Script *MapScope::findScript(String *name) + { + if(Script **script = pd->scriptStr.find(name)) + return *script; + else + return nullptr; + } + + // + // MapScope::findScript + // + Script *MapScope::findScript(Word name) + { + if(Script **script = pd->scriptInt.find(name)) + return *script; + else + return nullptr; + } + + // + // MapScope::freeThread + // + void MapScope::freeThread(Thread *thread) + { + auto itr = pd->scriptThread.find(thread->script); + if(itr && *itr == thread) + *itr = nullptr; + + env->freeThread(thread); + } + + // + // MapScope::getModuleScope + // + ModuleScope *MapScope::getModuleScope(Module *module) + { + return pd->scopes.find(module); + } + + // + // MapScope::getString + // + String *MapScope::getString(Word idx) const + { + if(idx & 0x80000000) + return &env->stringTable[~idx]; + + if(!module0 || idx >= module0->stringV.size()) + return &env->stringTable.getNone(); + + return module0->stringV[idx]; + } + + // + // MapScope::hasActiveThread + // + bool MapScope::hasActiveThread() const + { + for(auto &thread : threadActive) + { + if(thread.state != ThreadState::Inactive) + return true; + } + + return false; + } + + // + // MapScope::hasModules + // + bool MapScope::hasModules() const + { + return !pd->scopes.empty(); + } + + // + // MapScope::isScriptActive + // + bool MapScope::isScriptActive(Script *script) + { + auto itr = pd->scriptThread.find(script); + return itr && *itr && (*itr)->state != ThreadState::Inactive; + } + + // + // MapScope::loadModules + // + void MapScope::loadModules(Serial &in) + { + auto count = ReadVLN<std::size_t>(in); + std::vector<Module *> modules; + modules.reserve(count); + + for(auto n = count; n--;) + modules.emplace_back(env->getModule(env->readModuleName(in))); + + addModules(modules.data(), modules.size()); + + for(auto &module : modules) + pd->scopes.find(module)->loadState(in); + } + + // + // MapScope::loadState + // + void MapScope::loadState(Serial &in) + { + reset(); + + in.readSign(Signature::MapScope); + + env->readScriptActions(in, scriptAction); + active = in.in->get() != '\0'; + loadModules(in); + loadThreads(in); + + in.readSign(~Signature::MapScope); + } + + // + // MapScope::loadThreads + // + void MapScope::loadThreads(Serial &in) + { + for(auto n = ReadVLN<std::size_t>(in); n--;) + { + Thread *thread = env->getFreeThread(); + thread->link.insert(&threadActive); + thread->loadState(in); + + if(in.in->get()) + { + auto scrThread = pd->scriptThread.find(thread->script); + if(scrThread) + *scrThread = thread; + } + } + } + + // + // MapScope::lockStrings + // + void MapScope::lockStrings() const + { + for(auto &action : scriptAction) + action.lockStrings(env); + + for(auto &scope : pd->scopes) + scope.val.lockStrings(); + + for(auto &thread : threadActive) + thread.lockStrings(); + } + + // + // MapScope::refStrings + // + void MapScope::refStrings() const + { + for(auto &action : scriptAction) + action.refStrings(env); + + for(auto &scope : pd->scopes) + scope.val.refStrings(); + + for(auto &thread : threadActive) + thread.refStrings(); + } + + // + // MapScope::reset + // + void MapScope::reset() + { + // Stop any remaining threads and return them to free list. + while(threadActive.next->obj) + { + threadActive.next->obj->stop(); + env->freeThread(threadActive.next->obj); + } + + while(scriptAction.next->obj) + delete scriptAction.next->obj; + + active = false; + + pd->scopes.free(); + + pd->scriptInt.free(); + pd->scriptStr.free(); + pd->scriptThread.free(); + } + + // + // MapScope::saveModules + // + void MapScope::saveModules(Serial &out) const + { + WriteVLN(out, pd->scopes.size()); + + for(auto &scope : pd->scopes) + env->writeModuleName(out, scope.key->name); + + for(auto &scope : pd->scopes) + scope.val.saveState(out); + } + + // + // MapScope::saveState + // + void MapScope::saveState(Serial &out) const + { + out.writeSign(Signature::MapScope); + + env->writeScriptActions(out, scriptAction); + out.out->put(active ? '\1' : '\0'); + saveModules(out); + saveThreads(out); + + out.writeSign(~Signature::MapScope); + } + + // + // MapScope::saveThreads + // + void MapScope::saveThreads(Serial &out) const + { + WriteVLN(out, threadActive.size()); + for(auto &thread : threadActive) + { + thread.saveState(out); + + auto scrThread = pd->scriptThread.find(thread.script); + out.out->put(scrThread && *scrThread == &thread ? '\1' : '\0'); + } + } + + // + // MapScope::scriptPause + // + bool MapScope::scriptPause(Script *script) + { + auto itr = pd->scriptThread.find(script); + if(!itr || !*itr) + return false; + + switch((*itr)->state.state) + { + case ThreadState::Inactive: + case ThreadState::Paused: + case ThreadState::Stopped: + return false; + + default: + (*itr)->state = ThreadState::Paused; + return true; + } + } + + // + // MapScope::scriptPause + // + bool MapScope::scriptPause(ScriptName name, ScopeID scope) + { + if(scope != ScopeID{hub->global->id, hub->id, id}) + { + env->deferAction({scope, name, ScriptAction::Pause, {}}); + return true; + } + + if(Script *script = findScript(name)) + return scriptPause(script); + else + return false; + } + + // + // MapScope::scriptStart + // + bool MapScope::scriptStart(Script *script, ScriptStartInfo const &info) + { + auto itr = pd->scriptThread.find(script); + if(!itr) + return false; + + if(Thread *&thread = *itr) + { + switch(thread->state.state) + { + case ThreadState::Paused: + thread->state = ThreadState::Running; + return true; + + default: + return false; + } + } + else + { + thread = env->getFreeThread(); + thread->start(script, this, info.info, info.argV, info.argC); + if(info.func) info.func(thread); + if(info.funcc) info.funcc(thread); + return true; + } + } + + // + // MapScope::scriptStart + // + bool MapScope::scriptStart(ScriptName name, ScopeID scope, ScriptStartInfo const &info) + { + if(scope != ScopeID{hub->global->id, hub->id, id}) + { + env->deferAction({scope, name, ScriptAction::Start, {info.argV, info.argC}}); + return true; + } + + if(Script *script = findScript(name)) + return scriptStart(script, info); + else + return false; + } + + // + // MapScope::scriptStartForced + // + bool MapScope::scriptStartForced(Script *script, ScriptStartInfo const &info) + { + Thread *thread = env->getFreeThread(); + + thread->start(script, this, info.info, info.argV, info.argC); + if(info.func) info.func(thread); + if(info.funcc) info.funcc(thread); + return true; + } + + // + // MapScope::scriptStartForced + // + bool MapScope::scriptStartForced(ScriptName name, ScopeID scope, ScriptStartInfo const &info) + { + if(scope != ScopeID{hub->global->id, hub->id, id}) + { + env->deferAction({scope, name, ScriptAction::StartForced, {info.argV, info.argC}}); + return true; + } + + if(Script *script = findScript(name)) + return scriptStartForced(script, info); + else + return false; + } + + // + // MapScope::scriptStartResult + // + Word MapScope::scriptStartResult(Script *script, ScriptStartInfo const &info) + { + Thread *thread = env->getFreeThread(); + + thread->start(script, this, info.info, info.argV, info.argC); + if(info.func) info.func(thread); + if(info.funcc) info.funcc(thread); + thread->exec(); + + Word result = thread->result; + if(thread->state == ThreadState::Inactive) + freeThread(thread); + return result; + } + + // + // MapScope::scriptStartResult + // + Word MapScope::scriptStartResult(ScriptName name, ScriptStartInfo const &info) + { + if(Script *script = findScript(name)) + return scriptStartResult(script, info); + else + return 0; + } + + // + // MapScope::scriptStartType + // + Word MapScope::scriptStartType(Word type, ScriptStartInfo const &info) + { + Word result = 0; + + for(auto &script : pd->scriptThread) + { + if(script.key->type == type) + result += scriptStart(script.key, info); + } + + return result; + } + + // + // MapScope::scriptStartTypeForced + // + Word MapScope::scriptStartTypeForced(Word type, ScriptStartInfo const &info) + { + Word result = 0; + + for(auto &script : pd->scriptThread) + { + if(script.key->type == type) + result += scriptStartForced(script.key, info); + } + + return result; + } + + // + // MapScope::scriptStop + // + bool MapScope::scriptStop(Script *script) + { + auto itr = pd->scriptThread.find(script); + if(!itr || !*itr) + return false; + + switch((*itr)->state.state) + { + case ThreadState::Inactive: + case ThreadState::Stopped: + return false; + + default: + (*itr)->state = ThreadState::Stopped; + (*itr) = nullptr; + return true; + } + } + + // + // MapScope::scriptStop + // + bool MapScope::scriptStop(ScriptName name, ScopeID scope) + { + if(scope != ScopeID{hub->global->id, hub->id, id}) + { + env->deferAction({scope, name, ScriptAction::Stop, {}}); + return true; + } + + if(Script *script = findScript(name)) + return scriptStop(script); + else + return false; + } + + // + // MapScope::unlockStrings + // + void MapScope::unlockStrings() const + { + for(auto &action : scriptAction) + action.unlockStrings(env); + + for(auto &scope : pd->scopes) + scope.val.unlockStrings(); + + for(auto &thread : threadActive) + thread.unlockStrings(); + } + + // + // ModuleScope constructor + // + ModuleScope::ModuleScope(MapScope *map_, Module *module_) : + env {map_->env}, + map {map_}, + module{module_}, + + selfArrV{}, + selfRegV{} + { + // Set arrays and registers to refer to this scope's by default. + for(std::size_t i = 0; i != ArrC; ++i) arrV[i] = &selfArrV[i]; + for(std::size_t i = 0; i != RegC; ++i) regV[i] = &selfRegV[i]; + + // Apply initialization data from module. + + for(std::size_t i = 0; i != ArrC; ++i) + { + if(i < module->arrInitV.size()) + module->arrInitV[i].apply(selfArrV[i], module); + } + + for(std::size_t i = 0; i != RegC; ++i) + { + if(i < module->regInitV.size()) + selfRegV[i] = module->regInitV[i].getValue(module); + } + } + + // + // ModuleScope destructor + // + ModuleScope::~ModuleScope() + { + } + + // + // ModuleScope::import + // + void ModuleScope::import() + { + for(std::size_t i = 0, e = std::min<std::size_t>(ArrC, module->arrImpV.size()); i != e; ++i) + { + String *arrName = module->arrImpV[i]; + if(!arrName) continue; + + for(auto &imp : module->importV) + { + for(auto &impName : imp->arrNameV) + { + if(impName == arrName) + { + std::size_t impIdx = &impName - imp->arrNameV.data(); + if(impIdx >= ArrC) continue; + arrV[i] = &map->getModuleScope(imp)->selfArrV[impIdx]; + goto arr_found; + } + } + } + + arr_found:; + } + + for(std::size_t i = 0, e = std::min<std::size_t>(RegC, module->regImpV.size()); i != e; ++i) + { + String *regName = module->regImpV[i]; + if(!regName) continue; + + for(auto &imp : module->importV) + { + for(auto &impName : imp->regNameV) + { + if(impName == regName) + { + std::size_t impIdx = &impName - imp->regNameV.data(); + if(impIdx >= RegC) continue; + regV[i] = &map->getModuleScope(imp)->selfRegV[impIdx]; + goto reg_found; + } + } + } + + reg_found:; + } + } + + // + // ModuleScope::loadState + // + void ModuleScope::loadState(Serial &in) + { + in.readSign(Signature::ModuleScope); + + for(auto &arr : selfArrV) + arr.loadState(in); + + for(auto ® : selfRegV) + reg = ReadVLN<Word>(in); + + in.readSign(~Signature::ModuleScope); + } + + // + // ModuleScope::lockStrings + // + void ModuleScope::lockStrings() const + { + for(auto &arr : selfArrV) arr.lockStrings(env); + for(auto ® : selfRegV) ++env->getString(reg)->lock; + } + + // + // ModuleScope::refStrings + // + void ModuleScope::refStrings() const + { + for(auto &arr : selfArrV) arr.refStrings(env); + for(auto ® : selfRegV) env->getString(reg)->ref = true; + } + + // + // ModuleScope::saveState + // + void ModuleScope::saveState(Serial &out) const + { + out.writeSign(Signature::ModuleScope); + + for(auto &arr : selfArrV) + arr.saveState(out); + + for(auto ® : selfRegV) + WriteVLN(out, reg); + + out.writeSign(~Signature::ModuleScope); + } + + // + // ModuleScope::unlockStrings + // + void ModuleScope::unlockStrings() const + { + for(auto &arr : selfArrV) arr.unlockStrings(env); + for(auto ® : selfRegV) --env->getString(reg)->lock; + } +} + +// EOF + diff --git a/src/acs/vm/ACSVM/Scope.hpp b/src/acs/vm/ACSVM/Scope.hpp new file mode 100644 index 0000000000000000000000000000000000000000..761e65b2bb5ba76a4171f82fc17b1a5b07c94215 --- /dev/null +++ b/src/acs/vm/ACSVM/Scope.hpp @@ -0,0 +1,285 @@ +//----------------------------------------------------------------------------- +// +// Copyright (C) 2015-2017 David Hill +// +// See COPYING for license information. +// +//----------------------------------------------------------------------------- +// +// Scope classes. +// +//----------------------------------------------------------------------------- + +#ifndef ACSVM__Scope_H__ +#define ACSVM__Scope_H__ + +#include "Array.hpp" +#include "List.hpp" + + +//----------------------------------------------------------------------------| +// Types | +// + +namespace ACSVM +{ + extern "C" using MapScope_ScriptStartFuncC = void (*)(void *); + + // + // GlobalScope + // + class GlobalScope + { + public: + static constexpr std::size_t ArrC = 256; + static constexpr std::size_t RegC = 256; + + + GlobalScope(GlobalScope const &) = delete; + GlobalScope(Environment *env, Word id); + ~GlobalScope(); + + std::size_t countActiveThread() const; + + void exec(); + + void freeHubScope(HubScope *scope); + + HubScope *getHubScope(Word id); + + bool hasActiveThread() const; + + void lockStrings() const; + + void loadState(Serial &in); + + void refStrings() const; + + void reset(); + + void saveState(Serial &out) const; + + void unlockStrings() const; + + Environment *const env; + Word const id; + + Array arrV[ArrC]; + Word regV[RegC]; + + ListLink<GlobalScope> hashLink; + ListLink<ScriptAction> scriptAction; + + bool active; + + private: + struct PrivData; + + PrivData *pd; + }; + + // + // HubScope + // + class HubScope + { + public: + static constexpr std::size_t ArrC = 256; + static constexpr std::size_t RegC = 256; + + + HubScope(HubScope const &) = delete; + HubScope(GlobalScope *global, Word id); + ~HubScope(); + + std::size_t countActiveThread() const; + + void exec(); + + void freeMapScope(MapScope *scope); + + MapScope *getMapScope(Word id); + + bool hasActiveThread() const; + + void lockStrings() const; + + void loadState(Serial &in); + + void refStrings() const; + + void reset(); + + void saveState(Serial &out) const; + + void unlockStrings() const; + + Environment *const env; + GlobalScope *const global; + Word const id; + + Array arrV[ArrC]; + Word regV[RegC]; + + ListLink<HubScope> hashLink; + ListLink<ScriptAction> scriptAction; + + bool active; + + private: + struct PrivData; + + PrivData *pd; + }; + + // + // MapScope + // + class MapScope + { + public: + using ScriptStartFunc = void (*)(Thread *); + using ScriptStartFuncC = MapScope_ScriptStartFuncC; + + // + // ScriptStartInfo + // + class ScriptStartInfo + { + public: + ScriptStartInfo() : + argV{nullptr}, func{nullptr}, funcc{nullptr}, info{nullptr}, argC{0} {} + ScriptStartInfo(Word const *argV_, std::size_t argC_, + ThreadInfo const *info_ = nullptr, ScriptStartFunc func_ = nullptr) : + argV{argV_}, func{func_}, funcc{nullptr}, info{info_}, argC{argC_} {} + ScriptStartInfo(Word const *argV_, std::size_t argC_, + ThreadInfo const *info_, ScriptStartFuncC func_) : + argV{argV_}, func{nullptr}, funcc{func_}, info{info_}, argC{argC_} {} + + Word const *argV; + ScriptStartFunc func; + ScriptStartFuncC funcc; + ThreadInfo const *info; + std::size_t argC; + }; + + + MapScope(MapScope const &) = delete; + MapScope(HubScope *hub, Word id); + ~MapScope(); + + void addModules(Module *const *moduleV, std::size_t moduleC); + + std::size_t countActiveThread() const; + + void exec(); + + Script *findScript(ScriptName name); + Script *findScript(String *name); + Script *findScript(Word name); + + ModuleScope *getModuleScope(Module *module); + + String *getString(Word idx) const; + + bool hasActiveThread() const; + + bool hasModules() const; + + bool isScriptActive(Script *script); + + void loadState(Serial &in); + + void lockStrings() const; + + void refStrings() const; + + void reset(); + + void saveState(Serial &out) const; + + bool scriptPause(Script *script); + bool scriptPause(ScriptName name, ScopeID scope); + bool scriptStart(Script *script, ScriptStartInfo const &info); + bool scriptStart(ScriptName name, ScopeID scope, ScriptStartInfo const &info); + bool scriptStartForced(Script *script, ScriptStartInfo const &info); + bool scriptStartForced(ScriptName name, ScopeID scope, ScriptStartInfo const &info); + Word scriptStartResult(Script *script, ScriptStartInfo const &info); + Word scriptStartResult(ScriptName name, ScriptStartInfo const &info); + Word scriptStartType(Word type, ScriptStartInfo const &info); + Word scriptStartTypeForced(Word type, ScriptStartInfo const &info); + bool scriptStop(Script *script); + bool scriptStop(ScriptName name, ScopeID scope); + + void unlockStrings() const; + + Environment *const env; + HubScope *const hub; + Word const id; + + ListLink<MapScope> hashLink; + ListLink<ScriptAction> scriptAction; + ListLink<Thread> threadActive; + + // Used for untagged string lookup. + Module *module0; + + bool active; + bool clampCallSpec; + + protected: + void freeThread(Thread *thread); + + private: + struct PrivData; + + void loadModules(Serial &in); + void loadThreads(Serial &in); + + void saveModules(Serial &out) const; + void saveThreads(Serial &out) const; + + PrivData *pd; + }; + + // + // ModuleScope + // + class ModuleScope + { + public: + static constexpr std::size_t ArrC = 256; + static constexpr std::size_t RegC = 256; + + + ModuleScope(ModuleScope const &) = delete; + ModuleScope(MapScope *map, Module *module); + ~ModuleScope(); + + void import(); + + void loadState(Serial &in); + + void lockStrings() const; + + void refStrings() const; + + void saveState(Serial &out) const; + + void unlockStrings() const; + + Environment *const env; + MapScope *const map; + Module *const module; + + Array *arrV[ArrC]; + Word *regV[RegC]; + + private: + Array selfArrV[ArrC]; + Word selfRegV[RegC]; + }; +} + +#endif//ACSVM__Scope_H__ + diff --git a/src/acs/vm/ACSVM/Script.cpp b/src/acs/vm/ACSVM/Script.cpp new file mode 100644 index 0000000000000000000000000000000000000000..15d2d94a732457b417d5a24b6361f80dcc18d3a9 --- /dev/null +++ b/src/acs/vm/ACSVM/Script.cpp @@ -0,0 +1,54 @@ +//----------------------------------------------------------------------------- +// +// Copyright (C) 2015-2017 David Hill +// +// See COPYING for license information. +// +//----------------------------------------------------------------------------- +// +// Script class. +// +//----------------------------------------------------------------------------- + +#include "Script.hpp" + +#include "Environment.hpp" +#include "Module.hpp" + + +//----------------------------------------------------------------------------| +// Extern Fumnctions | +// + +namespace ACSVM +{ + // + // Script constructor + // + Script::Script(Module *module_) : + module{module_}, + + name{}, + + argC {0}, + codeIdx{0}, + flags {0}, + locArrC{0}, + locRegC{module->env->scriptLocRegC}, + type {0}, + + flagClient{false}, + flagNet {false} + { + } + + // + // Script destructor + // + Script::~Script() + { + } +} + +// EOF + diff --git a/src/acs/vm/ACSVM/Script.hpp b/src/acs/vm/ACSVM/Script.hpp new file mode 100644 index 0000000000000000000000000000000000000000..33f8f490569ffc841e50af6ae49e5e36fd3fa3f3 --- /dev/null +++ b/src/acs/vm/ACSVM/Script.hpp @@ -0,0 +1,66 @@ +//----------------------------------------------------------------------------- +// +// Copyright (C) 2015-2017 David Hill +// +// See COPYING for license information. +// +//----------------------------------------------------------------------------- +// +// Script class. +// +//----------------------------------------------------------------------------- + +#ifndef ACSVM__Script_H__ +#define ACSVM__Script_H__ + +#include "Types.hpp" + + +//----------------------------------------------------------------------------| +// Types | +// + +namespace ACSVM +{ + // + // ScriptName + // + class ScriptName + { + public: + ScriptName() : s{nullptr}, i{0} {} + ScriptName(String *s_) : s{s_}, i{0} {} + ScriptName(String *s_, Word i_) : s{s_}, i{i_} {} + ScriptName(Word i_) : s{nullptr}, i{i_} {} + + String *s; + Word i; + }; + + // + // Script + // + class Script + { + public: + explicit Script(Module *module); + ~Script(); + + Module *const module; + + ScriptName name; + + Word argC; + Word codeIdx; + Word flags; + Word locArrC; + Word locRegC; + Word type; + + bool flagClient : 1; + bool flagNet : 1; + }; +} + +#endif//ACSVM__Script_H__ + diff --git a/src/acs/vm/ACSVM/Serial.cpp b/src/acs/vm/ACSVM/Serial.cpp new file mode 100644 index 0000000000000000000000000000000000000000..83edc0528be18b6f49003032a59ad0304e58533d --- /dev/null +++ b/src/acs/vm/ACSVM/Serial.cpp @@ -0,0 +1,98 @@ +//----------------------------------------------------------------------------- +// +// Copyright (C) 2017 David Hill +// +// See COPYING for license information. +// +//----------------------------------------------------------------------------- +// +// Serialization. +// +//----------------------------------------------------------------------------- + +#include "Serial.hpp" + +#include "BinaryIO.hpp" +#include "Error.hpp" + +#include <cstring> + + +//----------------------------------------------------------------------------| +// Extern Functions | +// + +namespace ACSVM +{ + // + // Serial::loadHead + // + void Serial::loadHead() + { + char buf[6] = {}; + in->read(buf, 6); + + if(std::memcmp(buf, "ACSVM\0", 6)) + throw SerialError{"invalid file signature"}; + + version = ReadVLN<unsigned int>(*in); + + auto flags = ReadVLN<std::uint_fast32_t>(*in); + signs = flags & 0x0001; + } + + // + // Serial::loadTail + // + void Serial::loadTail() + { + readSign(~Signature::Serial); + } + + // + // Serial::readSign + // + void Serial::readSign(Signature sign) + { + if(!signs) return; + + auto got = static_cast<Signature>(ReadLE4(*in)); + + if(sign != got) + throw SerialSignError{sign, got}; + } + + // + // Serial::saveHead + // + void Serial::saveHead() + { + out->write("ACSVM\0", 6); + WriteVLN(*out, 0); + + std::uint_fast32_t flags = 0; + if(signs) flags |= 0x0001; + WriteVLN(*out, flags); + } + + // + // Serial::saveTail + // + void Serial::saveTail() + { + writeSign(~Signature::Serial); + } + + // + // Serial::writeSign + // + void Serial::writeSign(Signature sign) + { + if(!signs) return; + + WriteLE4(*out, static_cast<std::uint32_t>(sign)); + } +} + +// EOF + diff --git a/src/acs/vm/ACSVM/Serial.hpp b/src/acs/vm/ACSVM/Serial.hpp new file mode 100644 index 0000000000000000000000000000000000000000..98e1673724d71d443615510e131f1ffc8e1a0bb5 --- /dev/null +++ b/src/acs/vm/ACSVM/Serial.hpp @@ -0,0 +1,89 @@ +//----------------------------------------------------------------------------- +// +// Copyright (C) 2017 David Hill +// +// See COPYING for license information. +// +//----------------------------------------------------------------------------- +// +// Serialization. +// +//----------------------------------------------------------------------------- + +#ifndef ACSVM__Serial_H__ +#define ACSVM__Serial_H__ + +#include "ID.hpp" + +#include <istream> +#include <ostream> + + +//----------------------------------------------------------------------------| +// Types | +// + +namespace ACSVM +{ + // + // Signature + // + enum class Signature : std::uint32_t + { + Array = MakeID("ARAY"), + Environment = MakeID("ENVI"), + GlobalScope = MakeID("GBLs"), + HubScope = MakeID("HUBs"), + MapScope = MakeID("MAPs"), + ModuleScope = MakeID("MODs"), + Serial = MakeID("SERI"), + Thread = MakeID("THRD"), + }; + + // + // Serial + // + class Serial + { + public: + Serial(std::istream &in_) : in{&in_} {} + Serial(std::ostream &out_) : out{&out_}, + version{0}, signs{false} {} + + operator std::istream & () {return *in;} + operator std::ostream & () {return *out;} + + void loadHead(); + void loadTail(); + + void readSign(Signature sign); + + void saveHead(); + void saveTail(); + + void writeSign(Signature sign); + + union + { + std::istream *const in; + std::ostream *const out; + }; + + unsigned int version; + bool signs; + }; +} + + +//----------------------------------------------------------------------------| +// Extern Functions | +// + +namespace ACSVM +{ + constexpr Signature operator ~ (Signature sign) + {return static_cast<Signature>(~static_cast<std::uint32_t>(sign));} +} + +#endif//ACSVM__Serial_H__ + diff --git a/src/acs/vm/ACSVM/Stack.hpp b/src/acs/vm/ACSVM/Stack.hpp new file mode 100644 index 0000000000000000000000000000000000000000..9fb72cc53e4e033357dab1e9396e5006bef93460 --- /dev/null +++ b/src/acs/vm/ACSVM/Stack.hpp @@ -0,0 +1,110 @@ +//----------------------------------------------------------------------------- +// +// Copyright (C) 2015 David Hill +// +// See COPYING for license information. +// +//----------------------------------------------------------------------------- +// +// Stack class. +// +//----------------------------------------------------------------------------- + +#ifndef ACSVM__Stack_H__ +#define ACSVM__Stack_H__ + +#include <climits> +#include <new> +#include <utility> + + +//----------------------------------------------------------------------------| +// Types | +// + +namespace ACSVM +{ + // + // Stack + // + // Stack container. + // + template<typename T> + class Stack + { + public: + Stack() : stack{nullptr}, stkEnd{nullptr}, stkPtr{nullptr} {} + ~Stack() {clear(); ::operator delete(stack);} + + // operator [] + T &operator [] (std::size_t idx) {return *(stkPtr - idx);} + + // begin + T *begin() {return stack;} + T const *begin() const {return stack;} + + // clear + void clear() {while(stkPtr != stack) (--stkPtr)->~T();} + + // drop + void drop() {(--stkPtr)->~T();} + void drop(std::size_t n) {while(n--) (--stkPtr)->~T();} + + // empty + bool empty() const {return stkPtr == stack;} + + // end + T *end() {return stkPtr;} + T const *end() const {return stkPtr;} + + // push + void push(T const &value) {new(stkPtr++) T( value );} + void push(T &&value) {new(stkPtr++) T(std::move(value));} + + // + // reserve + // + void reserve(std::size_t count) + { + if(static_cast<std::size_t>(stkEnd - stkPtr) >= count) + return; + + // Save pointers as indexes. + std::size_t idxEnd = stkEnd - stack; + std::size_t idxPtr = stkPtr - stack; + + // Calculate new array size. + if(SIZE_MAX / sizeof(T) - idxEnd < count * 2) + throw std::bad_alloc(); + + idxEnd += count * 2; + + // Allocate and initialize new array. + T *stackNew = static_cast<T *>(::operator new(idxEnd * sizeof(T))); + for(T *itrNew = stackNew, *itr = stack, *end = stkPtr; itr != end;) + { + new(itrNew++) T(std::move(*itr++)); + itr->~T(); + } + + // Free old array. + ::operator delete(stack); + + // Restore pointers. + stack = stackNew; + stkPtr = stack + idxPtr; + stkEnd = stack + idxEnd; + } + + // size + std::size_t size() const {return stkPtr - stack;} + + private: + T *stack; + T *stkEnd; + T *stkPtr; + }; +} + +#endif//ACSVM__Stack_H__ + diff --git a/src/acs/vm/ACSVM/Store.hpp b/src/acs/vm/ACSVM/Store.hpp new file mode 100644 index 0000000000000000000000000000000000000000..713b4a4b8d573a6d8e6587a78fef0a60083f8568 --- /dev/null +++ b/src/acs/vm/ACSVM/Store.hpp @@ -0,0 +1,150 @@ +//----------------------------------------------------------------------------- +// +// Copyright (C) 2015 David Hill +// +// See COPYING for license information. +// +//----------------------------------------------------------------------------- +// +// Store class. +// +//----------------------------------------------------------------------------- + +#ifndef ACSVM__Store_H__ +#define ACSVM__Store_H__ + +#include "Types.hpp" + +#include <new> +#include <utility> + + +//----------------------------------------------------------------------------| +// Types | +// + +namespace ACSVM +{ + // + // Store + // + // Manages storage area for locals. + // + template<typename T> + class Store + { + public: + Store() : store{nullptr}, storeEnd{nullptr}, active{nullptr}, activeEnd{nullptr} {} + ~Store() {clear(); ::operator delete(store);} + + // operator [] + T &operator [] (std::size_t idx) {return active[idx];} + + // + // alloc + // + void alloc(std::size_t count) + { + // Possibly reallocate underlying storage. + if(static_cast<std::size_t>(storeEnd - activeEnd) < count) + { + // Save pointers as indexes. + std::size_t activeIdx = active - store; + std::size_t activeEndIdx = activeEnd - store; + std::size_t storeEndIdx = storeEnd - store; + + // Calculate new array size. + if(SIZE_MAX / sizeof(T) - storeEndIdx < count * 2) + throw std::bad_alloc(); + + storeEndIdx += count * 2; + + // Allocate and initialize new array. + T *storeNew = static_cast<T *>(::operator new(storeEndIdx * sizeof(T))); + for(T *out = storeNew, *in = store, *end = activeEnd; in != end; ++out, ++in) + { + new(out) T(std::move(*in)); + in->~T(); + } + + // Free old array. + ::operator delete(store); + + // Restore pointers. + store = storeNew; + active = store + activeIdx; + activeEnd = store + activeEndIdx; + storeEnd = store + storeEndIdx; + } + + active = activeEnd; + while(count--) new(activeEnd++) T{}; + } + + // + // allocLoad + // + // Allocates storage for loading from saved state. countFull elements are + // value-initialized and count elements are made available. That is, they + // should correspond to a prior call to sizeFull and size, respectively. + // + void allocLoad(std::size_t countFull, std::size_t count) + { + clear(); + alloc(countFull); + active = activeEnd - count; + } + + // begin + T *begin() {return active;} + T const *begin() const {return active;} + + // beginFull + T *beginFull() {return store;} + T const *beginFull() const {return store;} + + // + // clear + // + void clear() + { + while(activeEnd != store) + (--activeEnd)->~T(); + + active = store; + } + + // dataFull + T const *dataFull() const {return store;} + + // end + T *end() {return activeEnd;} + T const *end() const {return activeEnd;} + + // + // free + // + // count must be the size (in elements) of the previous allocation. + // + void free(std::size_t count) + { + while(activeEnd != active) + (--activeEnd)->~T(); + + active -= count; + } + + // size + std::size_t size() const {return activeEnd - active;} + + // sizeFull + std::size_t sizeFull() const {return activeEnd - store;} + + private: + T *store, *storeEnd; + T *active, *activeEnd; + }; +} + +#endif//ACSVM__Store_H__ + diff --git a/src/acs/vm/ACSVM/String.cpp b/src/acs/vm/ACSVM/String.cpp new file mode 100644 index 0000000000000000000000000000000000000000..d0b0b59524d82ce6cef0a09556bff67f90e0e8c1 --- /dev/null +++ b/src/acs/vm/ACSVM/String.cpp @@ -0,0 +1,354 @@ +//----------------------------------------------------------------------------- +// +// Copyright (C) 2015 David Hill +// +// See COPYING for license information. +// +//----------------------------------------------------------------------------- +// +// String classes. +// +//----------------------------------------------------------------------------- + +#include "String.hpp" + +#include "BinaryIO.hpp" +#include "HashMap.hpp" + +#include <new> +#include <vector> + + +//----------------------------------------------------------------------------| +// Types | +// + +namespace ACSVM +{ + // + // StringTable::PrivData + // + struct StringTable::PrivData + { + std::vector<Word> freeIdx; + + HashMapKeyObj<StringData, String, &String::link> stringByData{64, 64}; + std::vector<String *> stringByIdx; + }; +} + + +//----------------------------------------------------------------------------| +// Extern Functions | +// + +namespace ACSVM +{ + // + // String constructor + // + String::String(StringData const &data, Word idx_) : + StringData{data}, lock{0}, idx{idx_}, len0(std::strlen(str)), link{this} + { + } + + // + // String destructor + // + String::~String() + { + } + + // + // String::Delete + // + void String::Delete(String *str) + { + str->~String(); + operator delete(str); + } + + // + // String::New + // + String *String::New(StringData const &data, Word idx) + { + String *str = static_cast<String *>(operator new(sizeof(String) + data.len + 1)); + char *buf = reinterpret_cast<char *>(str + 1); + + memcpy(buf, data.str, data.len); + buf[data.len] = '\0'; + + return new(str) String{{buf, data.len, data.hash}, idx}; + } + + // + // String::Read + // + String *String::Read(std::istream &in, Word idx) + { + std::size_t len = ReadVLN<std::size_t>(in); + + String *str = static_cast<String *>(operator new(sizeof(String) + len + 1)); + char *buf = reinterpret_cast<char *>(str + 1); + + in.read(buf, len); + buf[len] = '\0'; + + return new(str) String{{buf, len, StrHash(buf, len)}, idx}; + } + + // + // String::Write + // + void String::Write(std::ostream &out, String *in) + { + WriteVLN(out, in->len); + out.write(in->str, in->len); + } + + // + // StringTable constructor + // + StringTable::StringTable() : + strV{nullptr}, + strC{0}, + + strNone{String::New({"", 0, 0}, 0)}, + + pd{new PrivData} + { + } + + // + // StringTable move constructor + // + StringTable::StringTable(StringTable &&table) : + strV{table.strV}, + strC{table.strC}, + + strNone{table.strNone}, + + pd{table.pd} + { + table.strV = nullptr; + table.strC = 0; + + table.strNone = nullptr; + + table.pd = nullptr; + } + + // + // StringTable destructor + // + StringTable::~StringTable() + { + if(!pd) return; + + clear(); + + delete pd; + + String::Delete(strNone); + } + + // + // StringTable::operator [StringData] + // + String &StringTable::operator [] (StringData const &data) + { + if(auto str = pd->stringByData.find(data)) return *str; + + Word idx; + if(pd->freeIdx.empty()) + { + // Index has to fit within Word size. + // If size_t has an equal or lesser max, then the check is redundant, + // and some compilers warn about that kind of tautological comparison. + #if SIZE_MAX > UINT32_MAX + if(pd->stringByIdx.size() > UINT32_MAX) + throw std::bad_alloc(); + #endif + + idx = pd->stringByIdx.size(); + pd->stringByIdx.emplace_back(strNone); + strV = pd->stringByIdx.data(); + strC = pd->stringByIdx.size(); + } + else + { + idx = pd->freeIdx.back(); + pd->freeIdx.pop_back(); + } + + String *str = String::New(data, idx); + pd->stringByIdx[idx] = str; + pd->stringByData.insert(str); + return *str; + } + + // + // StringTable::clear + // + void StringTable::clear() + { + for(auto &str : pd->stringByIdx) + { + if(str != strNone) + String::Delete(str); + } + + pd->freeIdx.clear(); + pd->stringByData.clear(); + pd->stringByIdx.clear(); + + strV = nullptr; + strC = 0; + } + + // + // StringTable::collectBegin + // + void StringTable::collectBegin() + { + for(auto &str : pd->stringByData) + str.ref = false; + } + + // + // StringTable.collectEnd + // + void StringTable::collectEnd() + { + for(auto itr = pd->stringByData.begin(), end = pd->stringByData.end(); itr != end;) + { + if(!itr->ref && !itr->lock) + { + String &str = *itr++; + pd->stringByIdx[str.idx] = strNone; + pd->freeIdx.push_back(str.idx); + pd->stringByData.unlink(&str); + String::Delete(&str); + } + else + ++itr; + } + } + + // + // StringTable::loadState + // + void StringTable::loadState(std::istream &in) + { + if(pd) + { + clear(); + } + else + { + pd = new PrivData; + strNone = String::New({"", 0, 0}, 0); + } + + auto count = ReadVLN<std::size_t>(in); + + pd->stringByIdx.resize(count); + strV = pd->stringByIdx.data(); + strC = pd->stringByIdx.size(); + + for(std::size_t idx = 0; idx != count; ++idx) + { + if(in.get()) + { + String *str = String::Read(in, idx); + str->lock = ReadVLN<std::size_t>(in); + pd->stringByIdx[idx] = str; + pd->stringByData.insert(str); + } + else + { + pd->stringByIdx[idx] = strNone; + pd->freeIdx.emplace_back(idx); + } + } + } + + // + // StringTable::saveState + // + void StringTable::saveState(std::ostream &out) const + { + WriteVLN(out, pd->stringByIdx.size()); + + for(String *&str : pd->stringByIdx) + { + if(str != strNone) + { + out << '\1'; + + String::Write(out, str); + WriteVLN(out, str->lock); + } + else + out << '\0'; + } + } + + // + // StringTable::size + // + std::size_t StringTable::size() const + { + return pd->stringByData.size(); + } + + // + // StrDup + // + std::unique_ptr<char[]> StrDup(char const *str) + { + return StrDup(str, std::strlen(str)); + } + + // + // StrDup + // + std::unique_ptr<char[]> StrDup(char const *str, std::size_t len) + { + std::unique_ptr<char[]> dup{new char[len + 1]}; + std::memcpy(dup.get(), str, len); + dup[len] = '\0'; + + return dup; + } + + // + // StrHash + // + std::size_t StrHash(char const *str) + { + std::size_t hash = 0; + + if(str) while(*str) + hash = hash * 5 + static_cast<unsigned char>(*str++); + + return hash; + } + + // + // StrHash + // + std::size_t StrHash(char const *str, std::size_t len) + { + std::size_t hash = 0; + + while(len--) + hash = hash * 5 + static_cast<unsigned char>(*str++); + + return hash; + } +} + +// EOF + diff --git a/src/acs/vm/ACSVM/String.hpp b/src/acs/vm/ACSVM/String.hpp new file mode 100644 index 0000000000000000000000000000000000000000..7c46e4f9205bb4572c71e30cedeff6d8d962e0c2 --- /dev/null +++ b/src/acs/vm/ACSVM/String.hpp @@ -0,0 +1,159 @@ +//----------------------------------------------------------------------------- +// +// Copyright (C) 2015 David Hill +// +// See COPYING for license information. +// +//----------------------------------------------------------------------------- +// +// String classes. +// +//----------------------------------------------------------------------------- + +#ifndef ACSVM__String_H__ +#define ACSVM__String_H__ + +#include "List.hpp" +#include "Types.hpp" + +#include <cstring> +#include <functional> +#include <memory> + + +//----------------------------------------------------------------------------| +// Types | +// + +namespace ACSVM +{ + std::size_t StrHash(char const *str, std::size_t len); + + // + // StringData + // + // Stores basic string information. Does not manage the storage for the + // string data. + // + class StringData + { + public: + StringData(char const *first, char const *last) : + str{first}, len(last - first), hash{StrHash(str, len)} {} + StringData(char const *str_, std::size_t len_) : + str{str_}, len{len_}, hash{StrHash(str, len)} {} + StringData(char const *str_, std::size_t len_, std::size_t hash_) : + str{str_}, len{len_}, hash{hash_} {} + + bool operator == (StringData const &r) const + {return hash == r.hash && len == r.len && !std::memcmp(str, r.str, len);} + + char const *const str; + std::size_t const len; + std::size_t const hash; + }; + + // + // String + // + // Indexed string data. + // + class String : public StringData + { + public: + std::size_t lock; + + Word const idx; // Index into table. + Word const len0; // Null-terminated length. + + bool ref; + + char get(std::size_t i) const {return i < len ? str[i] : '\0';} + + + friend class StringTable; + + private: + String(StringData const &data, Word idx); + ~String(); + + ListLink<String> link; + + + static void Delete(String *str); + + static String *New(StringData const &data, Word idx); + + static String *Read(std::istream &in, Word idx); + + static void Write(std::ostream &out, String *in); + }; + + // + // StringTable + // + class StringTable + { + public: + StringTable(); + StringTable(StringTable &&table); + ~StringTable(); + + String &operator [] (Word idx) const + {return idx < strC ? *strV[idx] : *strNone;} + String &operator [] (StringData const &data); + + void clear(); + + void collectBegin(); + void collectEnd(); + + String &getNone() {return *strNone;} + + void loadState(std::istream &in); + + void saveState(std::ostream &out) const; + + std::size_t size() const; + + private: + struct PrivData; + + String **strV; + std::size_t strC; + + String *strNone; + + PrivData *pd; + }; +} + +namespace std +{ + // + // hash<::ACSVM::StringData> + // + template<> + struct hash<::ACSVM::StringData> + { + size_t operator () (::ACSVM::StringData const &data) const + {return data.hash;} + }; +} + + +//----------------------------------------------------------------------------| +// Extern Functions | +// + +namespace ACSVM +{ + std::unique_ptr<char[]> StrDup(char const *str); + std::unique_ptr<char[]> StrDup(char const *str, std::size_t len); + + std::size_t StrHash(char const *str); + std::size_t StrHash(char const *str, std::size_t len); +} + +#endif//ACSVM__String_H__ + diff --git a/src/acs/vm/ACSVM/Thread.cpp b/src/acs/vm/ACSVM/Thread.cpp new file mode 100644 index 0000000000000000000000000000000000000000..c4d19a7a8c30a070656f35a3732a6804ba627340 --- /dev/null +++ b/src/acs/vm/ACSVM/Thread.cpp @@ -0,0 +1,292 @@ +//----------------------------------------------------------------------------- +// +// Copyright (C) 2015-2017 David Hill +// +// See COPYING for license information. +// +//----------------------------------------------------------------------------- +// +// Thread classes. +// +//----------------------------------------------------------------------------- + +#include "Thread.hpp" + +#include "Array.hpp" +#include "BinaryIO.hpp" +#include "Environment.hpp" +#include "Module.hpp" +#include "Scope.hpp" +#include "Script.hpp" +#include "Serial.hpp" + +#include <algorithm> + + +//----------------------------------------------------------------------------| +// Extern Functions | +// + +namespace ACSVM +{ + // + // Thread constructor + // + Thread::Thread(Environment *env_) : + env{env_}, + + link{this}, + + codePtr {nullptr}, + module {nullptr}, + scopeGbl{nullptr}, + scopeHub{nullptr}, + scopeMap{nullptr}, + scopeMod{nullptr}, + script {nullptr}, + delay {0}, + result {0} + { + } + + // + // Thread destructor + // + Thread::~Thread() + { + } + + // + // Thread::getInfo + // + ThreadInfo const *Thread::getInfo() const + { + return nullptr; + } + + // + // Thread::loadState + // + void Thread::loadState(Serial &in) + { + std::size_t count, countFull; + + in.readSign(Signature::Thread); + + module = env->getModule(env->readModuleName(in)); + codePtr = &module->codeV[ReadVLN<std::size_t>(in)]; + scopeGbl = env->getGlobalScope(ReadVLN<Word>(in)); + scopeHub = scopeGbl->getHubScope(ReadVLN<Word>(in)); + scopeMap = scopeHub->getMapScope(ReadVLN<Word>(in)); + scopeMod = scopeMap->getModuleScope(module); + script = env->readScript(in); + delay = ReadVLN<Word>(in); + result = ReadVLN<Word>(in); + + count = ReadVLN<std::size_t>(in); + callStk.clear(); callStk.reserve(count + CallStkSize); + while(count--) + callStk.push(readCallFrame(in)); + + count = ReadVLN<std::size_t>(in); + dataStk.clear(); dataStk.reserve(count + DataStkSize); + while(count--) + dataStk.push(ReadVLN<Word>(in)); + + countFull = ReadVLN<std::size_t>(in); + count = ReadVLN<std::size_t>(in); + localArr.allocLoad(countFull, count); + for(auto itr = localArr.beginFull(), end = localArr.end(); itr != end; ++itr) + itr->loadState(in); + + countFull = ReadVLN<std::size_t>(in); + count = ReadVLN<std::size_t>(in); + localReg.allocLoad(countFull, count); + for(auto itr = localReg.beginFull(), end = localReg.end(); itr != end; ++itr) + *itr = ReadVLN<Word>(in); + + countFull = ReadVLN<std::size_t>(in); + count = ReadVLN<std::size_t>(in); + in.in->read(printBuf.getLoadBuf(countFull, count), countFull); + + state.state = static_cast<ThreadState::State>(ReadVLN<int>(in)); + state.data = ReadVLN<Word>(in); + state.type = ReadVLN<Word>(in); + + in.readSign(~Signature::Thread); + } + + // + // Thread::lockStrings + // + void Thread::lockStrings() const + { + for(auto &data : dataStk) + ++env->getString(data)->lock; + + for(auto arr = localArr.beginFull(), end = localArr.end(); arr != end; ++arr) + arr->lockStrings(env); + + for(auto reg = localReg.beginFull(), end = localReg.end(); reg != end; ++reg) + ++env->getString(*reg)->lock; + + if(state == ThreadState::WaitScrS) + ++env->getString(state.data)->lock; + } + + // + // Thread::readCallFrame + // + CallFrame Thread::readCallFrame(Serial &in) const + { + CallFrame out; + + out.module = env->getModule(env->readModuleName(in)); + out.scopeMod = scopeMap->getModuleScope(out.module); + out.codePtr = &out.module->codeV[ReadVLN<std::size_t>(in)]; + out.locArrC = ReadVLN<std::size_t>(in); + out.locRegC = ReadVLN<std::size_t>(in); + + return out; + } + + // + // Thread::refStrings + // + void Thread::refStrings() const + { + for(auto &data : dataStk) + env->getString(data)->ref = true; + + for(auto arr = localArr.beginFull(), end = localArr.end(); arr != end; ++arr) + arr->refStrings(env); + + for(auto reg = localReg.beginFull(), end = localReg.end(); reg != end; ++reg) + env->getString(*reg)->ref = true; + + if(state == ThreadState::WaitScrS) + env->getString(state.data)->ref = true; + } + + // + // Thread::saveState + // + void Thread::saveState(Serial &out) const + { + out.writeSign(Signature::Thread); + + env->writeModuleName(out, module->name); + WriteVLN(out, codePtr - module->codeV.data()); + WriteVLN(out, scopeGbl->id); + WriteVLN(out, scopeHub->id); + WriteVLN(out, scopeMap->id); + env->writeScript(out, script); + WriteVLN(out, delay); + WriteVLN(out, result); + + WriteVLN(out, callStk.size()); + for(auto &call : callStk) + writeCallFrame(out, call); + + WriteVLN(out, dataStk.size()); + for(auto &data : dataStk) + WriteVLN(out, data); + + WriteVLN(out, localArr.sizeFull()); + WriteVLN(out, localArr.size()); + for(auto itr = localArr.beginFull(), end = localArr.end(); itr != end; ++itr) + itr->saveState(out); + + WriteVLN(out, localReg.sizeFull()); + WriteVLN(out, localReg.size()); + for(auto itr = localReg.beginFull(), end = localReg.end(); itr != end; ++itr) + WriteVLN(out, *itr); + + WriteVLN(out, printBuf.sizeFull()); + WriteVLN(out, printBuf.size()); + out.out->write(printBuf.dataFull(), printBuf.sizeFull()); + + WriteVLN<int>(out, state.state); + WriteVLN(out, state.data); + WriteVLN(out, state.type); + + out.writeSign(~Signature::Thread); + } + + // + // Thread::start + // + void Thread::start(Script *script_, MapScope *map, ThreadInfo const *, + Word const *argV, Word argC) + { + link.insert(&map->threadActive); + + script = script_; + module = script->module; + codePtr = &module->codeV[script->codeIdx]; + + scopeMod = map->getModuleScope(module); + scopeMap = map; + scopeHub = scopeMap->hub; + scopeGbl = scopeHub->global; + + callStk.reserve(CallStkSize); + dataStk.reserve(DataStkSize); + localArr.alloc(script->locArrC); + localReg.alloc(script->locRegC); + + std::copy(argV, argV + std::min<Word>(argC, script->argC), &localReg[0]); + + delay = 0; + result = 0; + state = ThreadState::Running; + } + + // + // Thread::stop + // + void Thread::stop() + { + // Release execution resources. + callStk.clear(); + dataStk.clear(); + localArr.clear(); + localReg.clear(); + printBuf.clear(); + + // Set state. + state = ThreadState::Inactive; + } + + // + // Thread::unlockStrings + // + void Thread::unlockStrings() const + { + for(auto &data : dataStk) + --env->getString(data)->lock; + + for(auto arr = localArr.beginFull(), end = localArr.end(); arr != end; ++arr) + arr->unlockStrings(env); + + for(auto reg = localReg.beginFull(), end = localReg.end(); reg != end; ++reg) + --env->getString(*reg)->lock; + + if(state == ThreadState::WaitScrS) + --env->getString(state.data)->lock; + } + + // + // Thread::writeCallFrame + // + void Thread::writeCallFrame(Serial &out, CallFrame const &in) const + { + env->writeModuleName(out, in.module->name); + WriteVLN(out, in.codePtr - in.module->codeV.data()); + WriteVLN(out, in.locArrC); + WriteVLN(out, in.locRegC); + } +} + +// EOF + diff --git a/src/acs/vm/ACSVM/Thread.hpp b/src/acs/vm/ACSVM/Thread.hpp new file mode 100644 index 0000000000000000000000000000000000000000..06d4b34d0d11173888ad51eb21f668862ff22c4b --- /dev/null +++ b/src/acs/vm/ACSVM/Thread.hpp @@ -0,0 +1,157 @@ +//----------------------------------------------------------------------------- +// +// Copyright (C) 2015-2017 David Hill +// +// See COPYING for license information. +// +//----------------------------------------------------------------------------- +// +// Thread classes. +// +//----------------------------------------------------------------------------- + +#ifndef ACSVM__Thread_H__ +#define ACSVM__Thread_H__ + +#include "List.hpp" +#include "PrintBuf.hpp" +#include "Stack.hpp" +#include "Store.hpp" + + +//----------------------------------------------------------------------------| +// Types | +// + +namespace ACSVM +{ + // + // CallFrame + // + // Stores a call frame for execution. + // + class CallFrame + { + public: + Word const *codePtr; + Module *module; + ModuleScope *scopeMod; + std::size_t locArrC; + std::size_t locRegC; + }; + + // + // ThreadState + // + class ThreadState + { + public: + enum State + { + Inactive, // Inactive thread. + Running, // Running. + Stopped, // Will go inactive on next exec. + Paused, // Paused by instruction. + WaitScrI, // Waiting on a numbered script. + WaitScrS, // Waiting on a named script. + WaitTag, // Waiting on tagged object. + }; + + + ThreadState() : state{Inactive}, data{0}, type{0} {} + ThreadState(State state_) : + state{state_}, data{0}, type{0} {} + ThreadState(State state_, Word data_) : + state{state_}, data{data_}, type{0} {} + ThreadState(State state_, Word data_, Word type_) : + state{state_}, data{data_}, type{type_} {} + + bool operator == (State s) const {return state == s;} + bool operator != (State s) const {return state != s;} + + State state; + + // Extra state data. Used by: + // WaitScrI - Script number. + // WaitScrS - Script name index. + // WaitTag - Tag number. + Word data; + + // Extra state data. Used by: + // WaitTag - Tag type. + Word type; + }; + + // + // ThreadInfo + // + // Derived classes can be used to pass extra information to started threads. + // + class ThreadInfo + { + public: + virtual ~ThreadInfo() {} + }; + + // + // Thread + // + class Thread + { + public: + Thread(Environment *env); + virtual ~Thread(); + + void exec(); + + virtual ThreadInfo const *getInfo() const; + + virtual void loadState(Serial &in); + + virtual void lockStrings() const; + + virtual void refStrings() const; + + virtual void saveState(Serial &out) const; + + virtual void start(Script *script, MapScope *map, ThreadInfo const *info, + Word const *argV, Word argC); + + virtual void stop(); + + virtual void unlockStrings() const; + + Environment *const env; + + ListLink<Thread> link; + + Stack<CallFrame> callStk; + Stack<Word> dataStk; + Store<Array> localArr; + Store<Word> localReg; + PrintBuf printBuf; + ThreadState state; + + Word const *codePtr; // Instruction pointer. + Module *module; // Current execution Module. + GlobalScope *scopeGbl; + HubScope *scopeHub; + MapScope *scopeMap; + ModuleScope *scopeMod; + Script *script; // Current execution Script. + Word delay; // Execution delay tics. + Word result; // Code-defined thread result. + + + static constexpr std::size_t CallStkSize = 8; + static constexpr std::size_t DataStkSize = 256; + + private: + CallFrame readCallFrame(Serial &in) const; + + void writeCallFrame(Serial &out, CallFrame const &in) const; + }; +} + +#endif//ACSVM__Thread_H__ + diff --git a/src/acs/vm/ACSVM/ThreadExec.cpp b/src/acs/vm/ACSVM/ThreadExec.cpp new file mode 100644 index 0000000000000000000000000000000000000000..6754800a4e3cd5594088c3a709932cd52db07e45 --- /dev/null +++ b/src/acs/vm/ACSVM/ThreadExec.cpp @@ -0,0 +1,643 @@ +//----------------------------------------------------------------------------- +// +// Copyright (C) 2015-2017 David Hill +// +// See COPYING for license information. +// +//----------------------------------------------------------------------------- +// +// Thread execution. +// +//----------------------------------------------------------------------------- + +#include "Thread.hpp" + +#include "Array.hpp" +#include "Code.hpp" +#include "Environment.hpp" +#include "Function.hpp" +#include "Jump.hpp" +#include "Module.hpp" +#include "Scope.hpp" +#include "Script.hpp" + + +//----------------------------------------------------------------------------| +// Macros | +// + +// +// ACSVM_DynamicGoto +// +// If nonzero, enables use of dynamic goto labels in core interpreter loop. +// Currently, only gcc syntax is supported. +// +#ifndef ACSVM_DynamicGoto +#if defined(__GNUC__) +#define ACSVM_DynamicGoto 1 +#else +#define ACSVM_DynamicGoto 0 +#endif +#endif + +// +// BranchTo +// +#define BranchTo(target) \ + do \ + { \ + codePtr = &module->codeV[(target)]; \ + CountBranch(); \ + } \ + while(0) + +// +// CountBranch +// +// Used to limit the number of branches to prevent infinite no-delay loops. +// +#define CountBranch() \ + if(branches && !--branches) \ + { \ + env->printKill(this, static_cast<Word>(KillType::BranchLimit), 0); \ + goto thread_stop; \ + } \ + else \ + ((void)0) + +// +// DeclCase +// +#if ACSVM_DynamicGoto +#define DeclCase(name) case_Code##name +#else +#define DeclCase(name) case static_cast<Word>(Code::name) +#endif + +// +// NextCase +// +#if ACSVM_DynamicGoto +#define NextCase() goto *cases[*codePtr++] +#else +#define NextCase() goto next_case +#endif + +// +// Op_* +// + +#define Op_AddU(lop) (dataStk.drop(), (lop) += dataStk[0]) +#define Op_AndU(lop) (dataStk.drop(), (lop) &= dataStk[0]) +#define Op_CmpI_GE(lop) (dataStk.drop(), OpFunc_CmpI_GE(lop, dataStk[0])) +#define Op_CmpI_GT(lop) (dataStk.drop(), OpFunc_CmpI_GT(lop, dataStk[0])) +#define Op_CmpI_LE(lop) (dataStk.drop(), OpFunc_CmpI_LE(lop, dataStk[0])) +#define Op_CmpI_LT(lop) (dataStk.drop(), OpFunc_CmpI_LT(lop, dataStk[0])) +#define Op_CmpU_EQ(lop) (dataStk.drop(), OpFunc_CmpU_EQ(lop, dataStk[0])) +#define Op_CmpU_NE(lop) (dataStk.drop(), OpFunc_CmpU_NE(lop, dataStk[0])) +#define Op_DecU(lop) (--(lop)) +#define Op_DivI(lop) (dataStk.drop(), OpFunc_DivI(lop, dataStk[0])) +#define Op_DivX(lop) (dataStk.drop(), OpFunc_DivX(lop, dataStk[0])) +#define Op_Drop(lop) (dataStk.drop(), (lop) = dataStk[0]) +#define Op_IncU(lop) (++(lop)) +#define Op_LAnd(lop) (dataStk.drop(), OpFunc_LAnd(lop, dataStk[0])) +#define Op_LOrI(lop) (dataStk.drop(), OpFunc_LOrI(lop, dataStk[0])) +#define Op_ModI(lop) (dataStk.drop(), OpFunc_ModI(lop, dataStk[0])) +#define Op_MulU(lop) (dataStk.drop(), (lop) *= dataStk[0]) +#define Op_MulX(lop) (dataStk.drop(), OpFunc_MulX(lop, dataStk[0])) +#define Op_OrIU(lop) (dataStk.drop(), (lop) |= dataStk[0]) +#define Op_OrXU(lop) (dataStk.drop(), (lop) ^= dataStk[0]) +#define Op_ShLU(lop) (dataStk.drop(), (lop) <<= dataStk[0] & 31) +#define Op_ShRI(lop) (dataStk.drop(), OpFunc_ShRI(lop, dataStk[0])) +#define Op_SubU(lop) (dataStk.drop(), (lop) -= dataStk[0]) + +// +// OpSet +// +#define OpSet(op) \ + DeclCase(op##_GblArr): \ + Op_##op(scopeGbl->arrV[*codePtr++][dataStk[1]]); dataStk.drop(); \ + NextCase(); \ + DeclCase(op##_GblReg): \ + Op_##op(scopeGbl->regV[*codePtr++]); \ + NextCase(); \ + DeclCase(op##_HubArr): \ + Op_##op(scopeHub->arrV[*codePtr++][dataStk[1]]); dataStk.drop(); \ + NextCase(); \ + DeclCase(op##_HubReg): \ + Op_##op(scopeHub->regV[*codePtr++]); \ + NextCase(); \ + DeclCase(op##_LocArr): \ + Op_##op(localArr[*codePtr++][dataStk[1]]); dataStk.drop(); \ + NextCase(); \ + DeclCase(op##_LocReg): \ + Op_##op(localReg[*codePtr++]); \ + NextCase(); \ + DeclCase(op##_ModArr): \ + Op_##op((*scopeMod->arrV[*codePtr++])[dataStk[1]]); dataStk.drop(); \ + NextCase(); \ + DeclCase(op##_ModReg): \ + Op_##op(*scopeMod->regV[*codePtr++]); \ + NextCase() + + +//----------------------------------------------------------------------------| +// Static Functions | +// + +namespace ACSVM +{ + // + // OpFunc_CmpI_GE + // + static inline void OpFunc_CmpI_GE(Word &lop, Word rop) + { + lop = static_cast<SWord>(lop) >= static_cast<SWord>(rop); + } + + // + // OpFunc_CmpI_GT + // + static inline void OpFunc_CmpI_GT(Word &lop, Word rop) + { + lop = static_cast<SWord>(lop) > static_cast<SWord>(rop); + } + + // + // OpFunc_CmpI_LE + // + static inline void OpFunc_CmpI_LE(Word &lop, Word rop) + { + lop = static_cast<SWord>(lop) <= static_cast<SWord>(rop); + } + + // + // OpFunc_CmpI_LT + // + static inline void OpFunc_CmpI_LT(Word &lop, Word rop) + { + lop = static_cast<SWord>(lop) < static_cast<SWord>(rop); + } + + // + // OpFunc_CmpU_EQ + // + static inline void OpFunc_CmpU_EQ(Word &lop, Word rop) + { + lop = lop == rop; + } + + // + // OpFunc_CmpU_NE + // + static inline void OpFunc_CmpU_NE(Word &lop, Word rop) + { + lop = lop != rop; + } + + // + // OpFunc_DivI + // + static inline void OpFunc_DivI(Word &lop, Word rop) + { + lop = rop ? static_cast<SWord>(lop) / static_cast<SWord>(rop) : 0; + } + + // + // OpFunc_DivX + // + static inline void OpFunc_DivX(Word &lop, Word rop) + { + if(rop) + lop = (SDWord(SWord(lop)) << 16) / SWord(rop); + else + lop = 0; + } + + // + // OpFunc_LAnd + // + static inline void OpFunc_LAnd(Word &lop, Word rop) + { + lop = lop && rop; + } + + // + // OpFunc_LOrI + // + static inline void OpFunc_LOrI(Word &lop, Word rop) + { + lop = lop || rop; + } + + // + // OpFunc_ModI + // + static inline void OpFunc_ModI(Word &lop, Word rop) + { + lop = rop ? static_cast<SWord>(lop) % static_cast<SWord>(rop) : 0; + } + + // + // OpFunc_MulX + // + static inline void OpFunc_MulX(Word &lop, Word rop) + { + lop = DWord(SDWord(SWord(lop)) * SWord(rop)) >> 16; + } + + // + // OpFunc_ShRI + // + static inline void OpFunc_ShRI(Word &lop, Word rop) + { + // TODO: Implement this without relying on sign-extending shift. + lop = static_cast<SWord>(lop) >> (rop & 31); + } +} + + +//----------------------------------------------------------------------------| +// Extern Functions | +// + +namespace ACSVM +{ + // + // Thread::exec + // + void Thread::exec() + { + if(delay && --delay) + return; + + auto branches = env->branchLimit; + + exec_intr: + switch(state.state) + { + case ThreadState::Inactive: return; + case ThreadState::Stopped: goto thread_stop; + case ThreadState::Paused: return; + + case ThreadState::Running: + if(delay) + return; + break; + + case ThreadState::WaitScrI: + if(scopeMap->isScriptActive(scopeMap->findScript(state.data))) + return; + state = ThreadState::Running; + break; + + case ThreadState::WaitScrS: + if(scopeMap->isScriptActive(scopeMap->findScript(scopeMap->getString(state.data)))) + return; + state = ThreadState::Running; + break; + + case ThreadState::WaitTag: + if(!module->env->checkTag(state.type, state.data)) + return; + state = ThreadState::Running; + break; + } + + #if ACSVM_DynamicGoto + static void const *const cases[] = + { + #define ACSVM_CodeList(name, ...) &&case_Code##name, + #include "CodeList.hpp" + }; + #endif + + #if ACSVM_DynamicGoto + NextCase(); + #else + next_case: switch(*codePtr++) + #endif + { + DeclCase(Nop): + NextCase(); + + DeclCase(Kill): + module->env->printKill(this, codePtr[0], codePtr[1]); + goto thread_stop; + + //================================================ + // Binary operator codes. + // + + OpSet(AddU); + OpSet(AndU); + OpSet(DivI); + OpSet(ModI); + OpSet(MulU); + OpSet(OrIU); + OpSet(OrXU); + OpSet(ShLU); + OpSet(ShRI); + OpSet(SubU); + + DeclCase(AddU): Op_AddU(dataStk[1]); NextCase(); + DeclCase(AndU): Op_AndU(dataStk[1]); NextCase(); + DeclCase(CmpI_GE): Op_CmpI_GE(dataStk[1]); NextCase(); + DeclCase(CmpI_GT): Op_CmpI_GT(dataStk[1]); NextCase(); + DeclCase(CmpI_LE): Op_CmpI_LE(dataStk[1]); NextCase(); + DeclCase(CmpI_LT): Op_CmpI_LT(dataStk[1]); NextCase(); + DeclCase(CmpU_EQ): Op_CmpU_EQ(dataStk[1]); NextCase(); + DeclCase(CmpU_NE): Op_CmpU_NE(dataStk[1]); NextCase(); + DeclCase(DivI): Op_DivI(dataStk[1]); NextCase(); + DeclCase(DivX): Op_DivX(dataStk[1]); NextCase(); + DeclCase(LAnd): Op_LAnd(dataStk[1]); NextCase(); + DeclCase(LOrI): Op_LOrI(dataStk[1]); NextCase(); + DeclCase(ModI): Op_ModI(dataStk[1]); NextCase(); + DeclCase(MulU): Op_MulU(dataStk[1]); NextCase(); + DeclCase(MulX): Op_MulX(dataStk[1]); NextCase(); + DeclCase(OrIU): Op_OrIU(dataStk[1]); NextCase(); + DeclCase(OrXU): Op_OrXU(dataStk[1]); NextCase(); + DeclCase(ShLU): Op_ShLU(dataStk[1]); NextCase(); + DeclCase(ShRI): Op_ShRI(dataStk[1]); NextCase(); + DeclCase(SubU): Op_SubU(dataStk[1]); NextCase(); + + //================================================ + // Call codes. + // + + DeclCase(Call_Lit): + { + Function *func; + + func = *codePtr < module->functionV.size() ? module->functionV[*codePtr] : nullptr; + ++codePtr; + + do_call: + if(!func) {BranchTo(0); NextCase();} + + // Reserve stack space. + callStk.reserve(CallStkSize); + dataStk.reserve(DataStkSize); + + // Push call frame. + callStk.push({codePtr, module, scopeMod, localArr.size(), localReg.size()}); + + // Apply function data. + codePtr = &func->module->codeV[func->codeIdx]; + module = func->module; + scopeMod = scopeMap->getModuleScope(module); + localArr.alloc(func->locArrC); + localReg.alloc(func->locRegC); + + // Read arguments. + dataStk.drop(func->argC); + memcpy(&localReg[0], &dataStk[0], func->argC * sizeof(Word)); + + NextCase(); + + DeclCase(Call_Stk): + dataStk.drop(); + func = env->getFunction(dataStk[0]); + goto do_call; + } + + DeclCase(CallFunc): + { + Word argc = *codePtr++; + Word func = *codePtr++; + dataStk.drop(argc); + if(env->callFunc(this, func, &dataStk[0], argc)) + goto exec_intr; + } + NextCase(); + + DeclCase(CallFunc_Lit): + { + Word argc = *codePtr++; + Word func = *codePtr++; + Word const *argv = codePtr; + codePtr += argc; + if(env->callFunc(this, func, argv, argc)) + goto exec_intr; + } + NextCase(); + + DeclCase(CallSpec): + { + Word argc = *codePtr++; + Word spec = *codePtr++; + dataStk.drop(argc); + env->callSpec(this, spec, &dataStk[0], argc); + } + NextCase(); + + DeclCase(CallSpec_Lit): + { + Word argc = *codePtr++; + Word spec = *codePtr++; + Word const *argv = codePtr; + codePtr += argc; + env->callSpec(this, spec, argv, argc); + } + NextCase(); + + DeclCase(CallSpec_R1): + { + Word argc = *codePtr++; + Word spec = *codePtr++; + dataStk.drop(argc); + dataStk.push(env->callSpec(this, spec, &dataStk[0], argc)); + } + NextCase(); + + DeclCase(Retn): + // If no call frames left, terminate the thread. + if(callStk.empty()) + goto thread_stop; + + // Apply call frame. + codePtr = callStk[1].codePtr; + module = callStk[1].module; + scopeMod = callStk[1].scopeMod; + localArr.free(callStk[1].locArrC); + localReg.free(callStk[1].locRegC); + + // Drop call frame. + callStk.drop(); + + NextCase(); + + //================================================ + // Drop codes. + // + + OpSet(Drop); + + DeclCase(Drop_Nul): + dataStk.drop(); + NextCase(); + + DeclCase(Drop_ScrRet): + dataStk.drop(); + result = dataStk[0]; + NextCase(); + + //================================================ + // Jump codes. + // + + DeclCase(Jcnd_Lit): + if(dataStk[1] == *codePtr++) + { + dataStk.drop(); + BranchTo(*codePtr); + } + else + ++codePtr; + NextCase(); + + DeclCase(Jcnd_Nil): + if(dataStk.drop(), dataStk[0]) + ++codePtr; + else + BranchTo(*codePtr); + NextCase(); + + DeclCase(Jcnd_Tab): + if(auto jump = module->jumpMapV[*codePtr++].table.find(dataStk[1])) + { + dataStk.drop(); + BranchTo(*jump); + } + NextCase(); + + DeclCase(Jcnd_Tru): + if(dataStk.drop(), dataStk[0]) + BranchTo(*codePtr); + else + ++codePtr; + NextCase(); + + DeclCase(Jump_Lit): + BranchTo(*codePtr); + NextCase(); + + DeclCase(Jump_Stk): + dataStk.drop(); + BranchTo(dataStk[0] < module->jumpV.size() ? module->jumpV[dataStk[0]].codeIdx : 0); + NextCase(); + + //================================================ + // Push codes. + // + + DeclCase(Pfun_Lit): + if(*codePtr < module->functionV.size()) + dataStk.push(module->functionV[*codePtr]->idx); + else + dataStk.push(0); + ++codePtr; + NextCase(); + + DeclCase(Pstr_Stk): + if(dataStk[1] < module->stringV.size()) + dataStk[1] = ~module->stringV[dataStk[1]]->idx; + NextCase(); + + DeclCase(Push_GblArr): dataStk[1] = scopeGbl->arrV[*codePtr++].find(dataStk[1]); NextCase(); + DeclCase(Push_GblReg): dataStk.push(scopeGbl->regV[*codePtr++]); NextCase(); + DeclCase(Push_HubArr): dataStk[1] = scopeHub->arrV[*codePtr++].find(dataStk[1]); NextCase(); + DeclCase(Push_HubReg): dataStk.push(scopeHub->regV[*codePtr++]); NextCase(); + DeclCase(Push_Lit): dataStk.push(*codePtr++); NextCase(); + DeclCase(Push_LitArr): for(auto i = *codePtr++; i--;) dataStk.push(*codePtr++); NextCase(); + DeclCase(Push_LocArr): dataStk[1] = localArr[*codePtr++].find(dataStk[1]); NextCase(); + DeclCase(Push_LocReg): dataStk.push(localReg[*codePtr++]); NextCase(); + DeclCase(Push_ModArr): dataStk[1] = scopeMod->arrV[*codePtr++]->find(dataStk[1]); NextCase(); + DeclCase(Push_ModReg): dataStk.push(*scopeMod->regV[*codePtr++]); NextCase(); + + DeclCase(Push_StrArs): + dataStk.drop(); + dataStk[1] = scopeMap->getString(dataStk[1])->get(dataStk[0]); + NextCase(); + + //================================================ + // Script control codes. + // + + DeclCase(ScrDelay): + dataStk.drop(); + delay = dataStk[0]; + goto exec_intr; + + DeclCase(ScrDelay_Lit): + delay = *codePtr++; + goto exec_intr; + + DeclCase(ScrHalt): + state = ThreadState::Paused; + goto exec_intr; + + DeclCase(ScrRestart): + BranchTo(script->codeIdx); + NextCase(); + + DeclCase(ScrTerm): + goto thread_stop; + + DeclCase(ScrWaitI): + dataStk.drop(); + state = {ThreadState::WaitScrI, dataStk[0]}; + goto exec_intr; + + DeclCase(ScrWaitI_Lit): + state = {ThreadState::WaitScrI, *codePtr++}; + goto exec_intr; + + DeclCase(ScrWaitS): + dataStk.drop(); + state = {ThreadState::WaitScrS, dataStk[0]}; + goto exec_intr; + + DeclCase(ScrWaitS_Lit): + state = {ThreadState::WaitScrS, *codePtr++}; + goto exec_intr; + + //================================================ + // Stack control codes. + // + + DeclCase(Copy): + {auto temp = dataStk[1]; dataStk.push(temp);} + NextCase(); + + DeclCase(Swap): + std::swap(dataStk[2], dataStk[1]); + NextCase(); + + //================================================ + // Unary operator codes. + // + + OpSet(DecU); + OpSet(IncU); + + DeclCase(InvU): + dataStk[1] = ~dataStk[1]; + NextCase(); + + DeclCase(NegI): + dataStk[1] = ~dataStk[1] + 1; + NextCase(); + + DeclCase(NotU): + dataStk[1] = !dataStk[1]; + NextCase(); + } + + thread_stop: + stop(); + } +} + +// EOF + diff --git a/src/acs/vm/ACSVM/Tracer.cpp b/src/acs/vm/ACSVM/Tracer.cpp new file mode 100644 index 0000000000000000000000000000000000000000..7e8e7e3b744fffec5dec94a4b50a6a1e07d69e08 --- /dev/null +++ b/src/acs/vm/ACSVM/Tracer.cpp @@ -0,0 +1,636 @@ +//----------------------------------------------------------------------------- +// +// Copyright (C) 2015 David Hill +// +// See COPYING for license information. +// +//----------------------------------------------------------------------------- +// +// Tracer classes. +// +//----------------------------------------------------------------------------- + +#include "Tracer.hpp" + +#include "BinaryIO.hpp" +#include "Code.hpp" +#include "CodeData.hpp" +#include "Environment.hpp" +#include "Error.hpp" +#include "Function.hpp" +#include "Jump.hpp" +#include "Module.hpp" +#include "Script.hpp" + + +//----------------------------------------------------------------------------| +// Extern Functions | +// + +namespace ACSVM +{ + // + // TracerACS0 constructor + // + TracerACS0::TracerACS0(Environment *env_, Byte const *data_, + std::size_t size_, bool compressed_) : + env {env_}, + codeFound {new Byte[size_]{}}, + codeIndex {new Word[size_]{}}, + codeC {0}, + jumpC {0}, + jumpMapC {0}, + data {data_}, + size {size_}, + compressed{compressed_} + { + } + + // + // TracerACS0 destructor + // + TracerACS0::~TracerACS0() + { + } + + // + // TracerACS0::getArgBytes + // + std::size_t TracerACS0::getArgBytes(CodeDataACS0 const *opData, std::size_t iter) + { + std::size_t argBytes; + + switch(opData->code) + { + case CodeACS0::Push_LitArrB: + if(size - iter < 1) throw ReadError(); + + return data[iter] + 1; + + case CodeACS0::Jcnd_Tab: + // Calculate alignment. + argBytes = ((iter + 3) & ~static_cast<std::size_t>(3)) - iter; + if(size - iter < argBytes) throw ReadError(); + + // Read the number of cases. + if(size - iter - argBytes < 4) throw ReadError(); + argBytes += ReadLE4(data + iter + argBytes) * 8 + 4; + + return argBytes; + + default: + argBytes = 0; + for(char const *s = opData->args; *s; ++s) switch(*s) + { + case 'B': argBytes += 1; break; + case 'H': argBytes += 2; break; + case 'W': argBytes += 4; break; + case 'b': argBytes += compressed ? 1 : 4; break; + case 'h': argBytes += compressed ? 2 : 4; break; + } + return argBytes; + } + } + + // + // TracerACS0::readCallFunc + // + std::pair<Word /*argc*/, Word /*func*/> TracerACS0::readCallFunc(std::size_t iter) + { + Word argc, func; + if(compressed) + { + argc = ReadLE1(data + iter + 0); + func = ReadLE2(data + iter + 1); + } + else + { + argc = ReadLE4(data + iter + 0); + func = ReadLE4(data + iter + 4); + } + return {argc, func}; + } + + // + // TracerACS0::readOpACS0 + // + std::tuple< + Word /*opCode*/, + CodeDataACS0 const * /*opData*/, + std::size_t /*opSize*/> + TracerACS0::readOpACS0(std::size_t iter) + { + if(compressed) + { + if(size - iter < 1) throw ReadError(); + + std::size_t opSize = 1; + Word opCode = data[iter]; + + if(opCode >= 240) + { + if(size - iter < 2) throw ReadError(); + ++opSize; + opCode = 240 + ((opCode - 240) << 8) + data[iter + 1]; + } + + return std::make_tuple(opCode, env->findCodeDataACS0(opCode), opSize); + } + else + { + if(size - iter < 4) throw ReadError(); + + Word opCode = ReadLE4(data + iter); + + return std::make_tuple(opCode, env->findCodeDataACS0(opCode), 4); + } + } + + // + // TracerACS0::setFound + // + bool TracerACS0::setFound(std::size_t first, std::size_t last) + { + Byte *begin = &codeFound[first]; + Byte *end = &codeFound[last]; + + std::size_t found = 0; + + for(Byte *itr = begin; itr != end; ++itr) + found += *itr; + + if(found) + { + if(found != last - first) throw ReadError(); + return false; + } + + for(Byte *itr = begin; itr != end; ++itr) + *itr = true; + + return true; + } + + // + // TracerACS0::trace + // + void TracerACS0::trace(Module *module) + { + // Add Kill to catch branches to zero. + codeC += 1 + env->getCodeData(Code::Kill)->argc; + + // Trace from entry points. + + for(Function *&func : module->functionV) + if(func && func->module == module) trace(func->codeIdx); + + for(Jump &jump : module->jumpV) + trace(jump.codeIdx); + + for(Script &scr : module->scriptV) + trace(scr.codeIdx); + + // Add Kill to catch execution past end. + codeC += 1 + env->getCodeData(Code::Kill)->argc; + } + + // + // TracerACS0::trace + // + void TracerACS0::trace(std::size_t iter) + { + for(std::size_t next;; iter = next) + { + // If at the end of the file, terminate tracer. Reaching here will + // result in a Kill, but the bytecode is otherwise well formed. + if(iter == size) return; + + // Whereas if iter is out of bounds, bytecode is malformed. + if(iter > size) throw ReadError(); + + // Read op. + CodeDataACS0 const *opData; + std::size_t opSize; + std::tie(std::ignore, opData, opSize) = readOpACS0(iter); + + // If no translation available, terminate trace. + if(!opData) + { + // Mark as found, so that the translator generates a KILL. + setFound(iter, iter + opSize); + codeC += 1 + env->getCodeData(Code::Kill)->argc; + return; + } + + std::size_t opSizeFull = opSize + getArgBytes(opData, iter + opSize); + + // If this op goes out of bounds, bytecode is malformed. + if(size - iter < opSizeFull) throw ReadError(); + + next = iter + opSizeFull; + + // If this op already found, terminate trace. + if(!setFound(iter, next)) + return; + + // Get data for translated op. + CodeData const *opTran = env->getCodeData(opData->transCode); + + // Count internal size of op. + switch(opData->code) + { + // -> Call(F) Drop_Nul() + case CodeACS0::Call_Nul: + codeC += 3; + break; + + case CodeACS0::CallSpec_1L: + case CodeACS0::CallSpec_2L: + case CodeACS0::CallSpec_3L: + case CodeACS0::CallSpec_4L: + case CodeACS0::CallSpec_5L: + case CodeACS0::CallSpec_6L: + case CodeACS0::CallSpec_7L: + case CodeACS0::CallSpec_8L: + case CodeACS0::CallSpec_9L: + case CodeACS0::CallSpec_10L: + case CodeACS0::CallSpec_1LB: + case CodeACS0::CallSpec_2LB: + case CodeACS0::CallSpec_3LB: + case CodeACS0::CallSpec_4LB: + case CodeACS0::CallSpec_5LB: + case CodeACS0::CallSpec_6LB: + case CodeACS0::CallSpec_7LB: + case CodeACS0::CallSpec_8LB: + case CodeACS0::CallSpec_9LB: + case CodeACS0::CallSpec_10LB: + case CodeACS0::Push_Lit2B: + case CodeACS0::Push_Lit3B: + case CodeACS0::Push_Lit4B: + case CodeACS0::Push_Lit5B: + case CodeACS0::Push_Lit6B: + case CodeACS0::Push_Lit7B: + case CodeACS0::Push_Lit8B: + case CodeACS0::Push_Lit9B: + case CodeACS0::Push_Lit10B: + codeC += opData->argc + 2; + break; + + case CodeACS0::Push_LitArrB: + codeC += data[iter + opSize] + 2; + break; + + // -> Push_Lit(0) Retn() + case CodeACS0::Retn_Nul: + codeC += 3; + break; + + case CodeACS0::CallFunc: + { + Word argc, func; + std::tie(argc, func) = readCallFunc(iter + opSize); + + FuncDataACS0 const *opFunc = env->findFuncDataACS0(func); + + if(!opFunc) + { + codeC += 1 + env->getCodeData(Code::Kill)->argc; + return; + } + + opTran = env->getCodeData(opFunc->getTransCode(argc)); + } + /* FALLTHRU */ + default: + if(opTran->code == Code::CallFunc_Lit) + codeC += opData->argc + 1 + 2; + else + codeC += opTran->argc + 1; + + if(opTran->code == Code::Kill) + return; + + break; + } + + // Special handling for branching ops. + switch(opData->code) + { + case CodeACS0::Jcnd_Nil: + case CodeACS0::Jcnd_Tru: + ++jumpC; + trace(ReadLE4(data + iter + opSize)); + break; + + case CodeACS0::Jcnd_Lit: + ++jumpC; + trace(ReadLE4(data + iter + opSize + 4)); + break; + + case CodeACS0::Jcnd_Tab: + { + std::size_t count, jumpIter; + + jumpIter = (iter + opSize + 3) & ~static_cast<std::size_t>(3); + count = ReadLE4(data + jumpIter); jumpIter += 4; + + ++jumpMapC; + + // Trace all of the jump targets. + for(; count--; jumpIter += 8) + trace(ReadLE4(data + jumpIter + 4)); + } + break; + + case CodeACS0::Jump_Lit: + ++jumpC; + next = ReadLE4(data + iter + opSize); + break; + + case CodeACS0::Jump_Stk: + // The target of this jump will get traced when the dynamic jump + // targets get traced. + return; + + case CodeACS0::Retn_Stk: + case CodeACS0::Retn_Nul: + case CodeACS0::ScrTerm: + return; + + default: + break; + } + } + } + + // + // TracerACS0::translate + // + void TracerACS0::translate(Module *module) + { + std::unique_ptr<Word*[]> jumps{new uint32_t *[jumpC]}; + + Word *codeItr = module->codeV.data(); + Word **jumpItr = jumps.get(); + auto jumpMapItr = module->jumpMapV.data(); + + // Add Kill to catch branches to zero. + *codeItr++ = static_cast<Word>(Code::Kill); + *codeItr++ = static_cast<Word>(KillType::OutOfBounds); + *codeItr++ = 0; + + for(std::size_t iter = 0, next; iter != size; iter = next) + { + // If no code at this index, skip it. + if(!codeFound[iter]) + { + next = iter + 1; + continue; + } + + // Record jump target. + codeIndex[iter] = codeItr - module->codeV.data(); + + // Read op. + Word opCode; + CodeDataACS0 const *opData; + std::size_t opSize; + std::tie(opCode, opData, opSize) = readOpACS0(iter); + + // If no translation available, generate Kill. + if(!opData) + { + *codeItr++ = static_cast<Word>(Code::Kill); + *codeItr++ = static_cast<Word>(KillType::UnknownCode); + *codeItr++ = opCode; + next = iter + opSize; + continue; + } + + // Calculate next index. + next = iter + opSize + getArgBytes(opData, iter + opSize); + + // Get data for translated op. + CodeData const *opTran = env->getCodeData(opData->transCode); + + // Generate internal op. + switch(opData->code) + { + case CodeACS0::Call_Nul: + *codeItr++ = static_cast<Word>(opData->transCode); + if(compressed) + *codeItr++ = ReadLE1(data + iter + opSize); + else + *codeItr++ = ReadLE4(data + iter + opSize); + *codeItr++ = static_cast<Word>(Code::Drop_Nul); + break; + + case CodeACS0::CallSpec_1: + case CodeACS0::CallSpec_2: + case CodeACS0::CallSpec_3: + case CodeACS0::CallSpec_4: + case CodeACS0::CallSpec_5: + case CodeACS0::CallSpec_5Ex: + case CodeACS0::CallSpec_6: + case CodeACS0::CallSpec_7: + case CodeACS0::CallSpec_8: + case CodeACS0::CallSpec_9: + case CodeACS0::CallSpec_10: + case CodeACS0::CallSpec_5R1: + case CodeACS0::CallSpec_10R1: + *codeItr++ = static_cast<Word>(opData->transCode); + *codeItr++ = opData->stackArgC; + goto trans_args; + + case CodeACS0::CallSpec_1L: + case CodeACS0::CallSpec_1LB: + case CodeACS0::CallSpec_2L: + case CodeACS0::CallSpec_2LB: + case CodeACS0::CallSpec_3L: + case CodeACS0::CallSpec_3LB: + case CodeACS0::CallSpec_4L: + case CodeACS0::CallSpec_4LB: + case CodeACS0::CallSpec_5L: + case CodeACS0::CallSpec_5LB: + case CodeACS0::CallSpec_6L: + case CodeACS0::CallSpec_6LB: + case CodeACS0::CallSpec_7L: + case CodeACS0::CallSpec_7LB: + case CodeACS0::CallSpec_8L: + case CodeACS0::CallSpec_8LB: + case CodeACS0::CallSpec_9L: + case CodeACS0::CallSpec_9LB: + case CodeACS0::CallSpec_10L: + case CodeACS0::CallSpec_10LB: + *codeItr++ = static_cast<Word>(opData->transCode); + *codeItr++ = opData->argc - 1; + goto trans_args; + + case CodeACS0::Jcnd_Tab: + { + std::size_t count, jumpIter; + + jumpIter = (iter + opSize + 3) & ~static_cast<std::size_t>(3); + count = ReadLE4(data + jumpIter); jumpIter += 4; + + *codeItr++ = static_cast<Word>(opData->transCode); + *codeItr++ = jumpMapItr - module->jumpMapV.data(); + + (jumpMapItr++)->loadJumps(data + jumpIter, count); + } + break; + + case CodeACS0::Push_LitArrB: + *codeItr++ = static_cast<Word>(opData->transCode); + iter += opSize; + for(std::size_t n = *codeItr++ = data[iter++]; n--;) + *codeItr++ = data[iter++]; + break; + + case CodeACS0::Push_Lit2B: + case CodeACS0::Push_Lit3B: + case CodeACS0::Push_Lit4B: + case CodeACS0::Push_Lit5B: + case CodeACS0::Push_Lit6B: + case CodeACS0::Push_Lit7B: + case CodeACS0::Push_Lit8B: + case CodeACS0::Push_Lit9B: + case CodeACS0::Push_Lit10B: + *codeItr++ = static_cast<Word>(opData->transCode); + *codeItr++ = opData->argc; + goto trans_args; + + case CodeACS0::Retn_Nul: + *codeItr++ = static_cast<Word>(Code::Push_Lit); + *codeItr++ = static_cast<Word>(0); + *codeItr++ = static_cast<Word>(opData->transCode); + break; + + case CodeACS0::CallFunc: + { + Word argc, func; + std::tie(argc, func) = readCallFunc(iter + opSize); + + FuncDataACS0 const *opFunc = env->findFuncDataACS0(func); + + if(!opFunc) + { + *codeItr++ = static_cast<Word>(Code::Kill); + *codeItr++ = static_cast<Word>(KillType::UnknownFunc); + *codeItr++ = func; + continue; + } + + opTran = env->getCodeData(opFunc->getTransCode(argc)); + + *codeItr++ = static_cast<Word>(opTran->code); + if(opTran->code == Code::Kill) + { + *codeItr++ = static_cast<Word>(KillType::UnknownFunc); + *codeItr++ = func; + continue; + } + else if(opTran->code == Code::CallFunc) + { + *codeItr++ = argc; + *codeItr++ = opFunc->transFunc; + } + } + break; + + default: + *codeItr++ = static_cast<Word>(opData->transCode); + if(opTran->code == Code::Kill) + { + *codeItr++ = static_cast<Word>(KillType::UnknownCode); + *codeItr++ = opCode; + continue; + } + else if(opTran->code == Code::CallFunc) + { + *codeItr++ = opData->stackArgC; + *codeItr++ = opData->transFunc; + } + else if(opTran->code == Code::CallFunc_Lit) + { + *codeItr++ = opData->argc; + *codeItr++ = opData->transFunc; + } + + trans_args: + // Convert arguments. + iter += opSize; + for(char const *a = opData->args; *a; ++a) switch(*a) + { + case 'B': *codeItr++ = ReadLE1(data + iter); iter += 1; break; + case 'H': *codeItr++ = ReadLE2(data + iter); iter += 2; break; + case 'W': *codeItr++ = ReadLE4(data + iter); iter += 4; break; + + case 'J': + *jumpItr++ = codeItr - 1; + break; + + case 'S': + if(*(codeItr - 1) < module->stringV.size()) + *(codeItr - 1) = ~module->stringV[*(codeItr - 1)]->idx; + break; + + case 'b': + if(compressed) + {*codeItr++ = ReadLE1(data + iter); iter += 1;} + else + {*codeItr++ = ReadLE4(data + iter); iter += 4;} + break; + + case 'h': + if(compressed) + {*codeItr++ = ReadLE2(data + iter); iter += 2;} + else + {*codeItr++ = ReadLE4(data + iter); iter += 4;} + break; + } + + break; + } + } + + // Add Kill to catch execution past end. + *codeItr++ = static_cast<Word>(Code::Kill); + *codeItr++ = static_cast<Word>(KillType::OutOfBounds); + *codeItr++ = 1; + + // Translate jumps. Has to be done after code in order to jump forward. + while(jumpItr != jumps.get()) + { + codeItr = *--jumpItr; + + if(*codeItr < size) + *codeItr = codeIndex[*codeItr]; + else + *codeItr = 0; + } + + // Translate entry points. + + for(Function *&func : module->functionV) + { + if(func && func->module == module) + func->codeIdx = func->codeIdx < size ? codeIndex[func->codeIdx] : 0; + } + + for(Jump &jump : module->jumpV) + jump.codeIdx = jump.codeIdx < size ? codeIndex[jump.codeIdx] : 0; + + for(JumpMap &jumpMap : module->jumpMapV) + { + for(auto &jump : jumpMap.table) + jump.val = jump.val < size ? codeIndex[jump.val] : 0; + } + + for(Script &scr : module->scriptV) + scr.codeIdx = scr.codeIdx < size ? codeIndex[scr.codeIdx] : 0; + } +} + +// EOF + diff --git a/src/acs/vm/ACSVM/Tracer.hpp b/src/acs/vm/ACSVM/Tracer.hpp new file mode 100644 index 0000000000000000000000000000000000000000..d0918946a1501796244d01514ff390751a5fe423 --- /dev/null +++ b/src/acs/vm/ACSVM/Tracer.hpp @@ -0,0 +1,76 @@ +//----------------------------------------------------------------------------- +// +// Copyright (C) 2015 David Hill +// +// See COPYING for license information. +// +//----------------------------------------------------------------------------- +// +// Tracer classes. +// +//----------------------------------------------------------------------------- + +#ifndef ACSVM__Tracer_H__ +#define ACSVM__Tracer_H__ + +#include "Types.hpp" + +#include <memory> + + +//----------------------------------------------------------------------------| +// Types | +// + +namespace ACSVM +{ + // + // TracerACS0 + // + // Traces input bytecode in ACS0 format for code paths, and then + // translates discovered codes. + // + class TracerACS0 + { + public: + TracerACS0(Environment *env, Byte const *data, std::size_t size, bool compressed); + ~TracerACS0(); + + void trace(Module *module); + + void translate(Module *module); + + Environment *env; + + std::unique_ptr<Byte[]> codeFound; + std::unique_ptr<Word[]> codeIndex; + std::size_t codeC; + + std::size_t jumpC; + + std::size_t jumpMapC; + + private: + std::size_t getArgBytes(CodeDataACS0 const *opData, std::size_t iter); + + std::pair<Word /*argc*/, Word /*func*/> readCallFunc(std::size_t iter); + + std::tuple< + Word /*opCode*/, + CodeDataACS0 const * /*opData*/, + std::size_t /*opSize*/> + readOpACS0(std::size_t iter); + + bool setFound(std::size_t first, std::size_t last); + + void trace(std::size_t iter); + + // Bytecode information. + Byte const *data; + std::size_t size; + bool compressed; + }; +} + +#endif//ACSVM__Tracer_H__ + diff --git a/src/acs/vm/ACSVM/Types.hpp b/src/acs/vm/ACSVM/Types.hpp new file mode 100644 index 0000000000000000000000000000000000000000..8da5445d570d176a0897071b8e86b9d1cfa70871 --- /dev/null +++ b/src/acs/vm/ACSVM/Types.hpp @@ -0,0 +1,81 @@ +//----------------------------------------------------------------------------- +// +// Copyright (C) 2015-2017 David Hill +// +// See COPYING for license information. +// +//----------------------------------------------------------------------------- +// +// Common typedefs and class forward declarations. +// +//----------------------------------------------------------------------------- + +#ifndef ACSVM__Types_H__ +#define ACSVM__Types_H__ + +#include <cinttypes> +#include <cstddef> + + +//----------------------------------------------------------------------------| +// Types | +// + +namespace ACSVM +{ + // Host platform byte. + // Should this be uint8_t? Short answer: No. + // Long answer: char is the smallest addressable unit in the implementation. + // That is, it is by definition the byte of the target platform. And it is + // required to be at least 8 bits wide, which is the only real requirement + // for loading ACS bytecode. + // Furthermore, uint8_t is only defined if the implementation has a type + // with exactly 8 data bits and no padding bits. So even if you wanted to + // unilaterally declare bytes to be 8 bits, the only type that can possibly + // satisfy uint8_t is unsigned char. If CHAR_BIT is not 8, then there can be + // no uint8_t. + using Byte = unsigned char; + + using DWord = std::uint64_t; + using SDWord = std::int64_t; + using SWord = std::int32_t; + using Word = std::uint32_t; + + enum class Code; + enum class CodeACS0; + enum class Func; + enum class FuncACS0; + enum class InitTag; + enum class Signature : std::uint32_t; + class Array; + class ArrayInit; + class CodeData; + class CodeDataACS0; + class Environment; + class FuncDataACS0; + class Function; + class GlobalScope; + class HubScope; + class Jump; + class JumpMap; + class MapScope; + class Module; + class ModuleName; + class ModuleScope; + class PrintBuf; + class ScopeID; + class Script; + class ScriptAction; + class ScriptName; + class Serial; + class String; + class Thread; + class ThreadInfo; + class ThreadState; + class WordInit; + + using CallFunc = bool (*)(Thread *thread, Word const *argv, Word argc); +} + +#endif//ACSVM__Types_H__ + diff --git a/src/acs/vm/ACSVM/Vector.hpp b/src/acs/vm/ACSVM/Vector.hpp new file mode 100644 index 0000000000000000000000000000000000000000..966bc6ea9a45f805bd99d164d84359671c5327c4 --- /dev/null +++ b/src/acs/vm/ACSVM/Vector.hpp @@ -0,0 +1,140 @@ +//----------------------------------------------------------------------------- +// +// Copyright (C) 2015-2016 David Hill +// +// See COPYING for license information. +// +//----------------------------------------------------------------------------- +// +// Vector class. +// +//----------------------------------------------------------------------------- + +#ifndef ACSVM__Vector_H__ +#define ACSVM__Vector_H__ + +#include "Types.hpp" + +#include <new> +#include <utility> + + +//----------------------------------------------------------------------------| +// Types | +// + +namespace ACSVM +{ + // + // Vector + // + // Runtime sized array. + // + template<typename T> + class Vector + { + public: + using const_iterator = T const *; + using iterator = T *; + using size_type = std::size_t; + + + Vector() : dataV{nullptr}, dataC{0} {} + Vector(Vector<T> const &) = delete; + Vector(Vector<T> &&v) : dataV{v.dataV}, dataC{v.dataC} + {v.dataV = nullptr; v.dataC = 0;} + Vector(size_type count) : dataV{nullptr}, dataC{0} {alloc(count);} + + Vector(T const *v, size_type c) + { + dataC = c; + dataV = static_cast<T *>(::operator new(sizeof(T) * dataC)); + + for(T *itr = dataV, *last = itr + dataC; itr != last; ++itr) + new(itr) T{*v++}; + } + + ~Vector() {free();} + + T &operator [] (size_type i) {return dataV[i];} + + Vector<T> &operator = (Vector<T> &&v) {swap(v); return *this;} + + // + // alloc + // + template<typename... Args> + void alloc(size_type count, Args const &...args) + { + if(dataV) free(); + + dataC = count; + dataV = static_cast<T *>(::operator new(sizeof(T) * dataC)); + + for(T *itr = dataV, *last = itr + dataC; itr != last; ++itr) + new(itr) T{args...}; + } + + // begin + iterator begin() {return dataV;} + const_iterator begin() const {return dataV;} + + // data + T *data() {return dataV;} + + // end + iterator end() {return dataV + dataC;} + const_iterator end() const {return dataV + dataC;} + + // + // free + // + void free() + { + if(!dataV) return; + + for(T *itr = dataV + dataC; itr != dataV;) + (--itr)->~T(); + + ::operator delete(dataV); + dataV = nullptr; + dataC = 0; + } + + // + // realloc + // + template<typename... Args> + void realloc(size_type count, Args const &...args) + { + if(count == dataC) return; + + Vector<T> old{std::move(*this)}; + + dataC = count; + dataV = static_cast<T *>(::operator new(sizeof(T) * dataC)); + + T *itr = begin(), *last = end(), *oldItr = old.begin(); + T *mid = count > old.size() ? dataV + old.size() : last; + + while(itr != mid) + new(itr++) T{std::move(*oldItr++)}; + while(itr != last) + new(itr++) T{args...}; + } + + // size + size_type size() const {return dataC;} + + // swap + void swap(Vector<T> &v) + {std::swap(dataV, v.dataV); std::swap(dataC, v.dataC);} + + private: + T *dataV; + size_type dataC; + }; +} + +#endif//ACSVM__Vector_H__ + diff --git a/src/acs/vm/CMakeLists.txt b/src/acs/vm/CMakeLists.txt new file mode 100644 index 0000000000000000000000000000000000000000..3116012c67ec1e89d47c5e3afbb3e17ad523c889 --- /dev/null +++ b/src/acs/vm/CMakeLists.txt @@ -0,0 +1,193 @@ +##----------------------------------------------------------------------------- +## +## Copyright (C) 2015-2016 David Hill +## +## See COPYING for license information. +## +##----------------------------------------------------------------------------- +## +## Root CMake file. +## +##----------------------------------------------------------------------------- + +cmake_minimum_required(VERSION 2.6) + +cmake_policy(SET CMP0017 NEW) + +project(acsvm) + +include(CheckCCompilerFlag) +include(CheckCXXCompilerFlag) + + +##----------------------------------------------------------------------------| +## Functions | +## + +## +## ACSVM_INSTALL_EXE +## +function(ACSVM_INSTALL_EXE name) + if(ACSVM_INSTALL_EXE) + install(TARGETS ${name} + RUNTIME DESTINATION bin + LIBRARY DESTINATION lib + ARCHIVE DESTINATION lib + ) + endif() +endfunction() + +## +## ACSVM_INSTALL_LIB +## +function(ACSVM_INSTALL_LIB name) + if(ACSVM_INSTALL_LIB) + if(ACSVM_INSTALL_API) + install(TARGETS ${name} + RUNTIME DESTINATION bin + LIBRARY DESTINATION lib + ARCHIVE DESTINATION lib + ) + elseif(ACSVM_SHARED) + install(TARGETS ${name} + RUNTIME DESTINATION bin + LIBRARY DESTINATION lib + ) + endif() + endif() +endfunction() + +## +## ACSVM_TRY_C_FLAG +## +function(ACSVM_TRY_C_FLAG flag name) + CHECK_C_COMPILER_FLAG(${flag} ACSVM_C_FLAG_${name}) + + if(ACSVM_C_FLAG_${name}) + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${flag}" PARENT_SCOPE) + endif() +endfunction() + +## +## ACSVM_TRY_CXX_FLAG +## +function(ACSVM_TRY_CXX_FLAG flag name) + CHECK_CXX_COMPILER_FLAG(${flag} ACSVM_CXX_FLAG_${name}) + + if(ACSVM_CXX_FLAG_${name}) + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${flag}" PARENT_SCOPE) + endif() +endfunction() + + +##----------------------------------------------------------------------------| +## Environment Detection | +## + +set(ACSVM_SHARED_DEFAULT ON) + +if(NOT ACSVM_NOFLAGS) + if(CMAKE_C_COMPILER_ID STREQUAL "GNU" OR CMAKE_C_COMPILER_ID STREQUAL "Clang") + ACSVM_TRY_C_FLAG(-Wall Wall) + ACSVM_TRY_C_FLAG(-Wextra Wextra) + + ACSVM_TRY_C_FLAG(-std=c11 STD_C) + endif() + + if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU" OR CMAKE_CXX_COMPILER_ID STREQUAL "Clang") + ACSVM_TRY_CXX_FLAG(-Wall Wall) + ACSVM_TRY_CXX_FLAG(-Wextra Wextra) + + ACSVM_TRY_CXX_FLAG(-std=c++11 STD_CXX) + endif() +endif() + +if(MSVC) + # Disable shared by default, as the source does not contain the needed + # declaration annotations to make that work under MSVC. + set(ACSVM_SHARED_DEFAULT OFF) +endif() + + +##----------------------------------------------------------------------------| +## Variables | +## + +## +## ACSVM_INSTALL_API +## +if(NOT DEFINED ACSVM_INSTALL_API) + set(ACSVM_INSTALL_API ON CACHE BOOL "Install ACSVM headers.") +endif() + +## +## ACSVM_INSTALL_DOC +## +if(NOT DEFINED ACSVM_INSTALL_DOC) + set(ACSVM_INSTALL_DOC ON CACHE BOOL "Install ACSVM documentation.") +endif() + +## +## ACSVM_INSTALL_EXE +## +if(NOT DEFINED ACSVM_INSTALL_EXE) + set(ACSVM_INSTALL_EXE ON CACHE BOOL "Install ACSVM executables.") +endif() + +## +## ACSVM_INSTALL_LIB +## +if(NOT DEFINED ACSVM_INSTALL_LIB) + set(ACSVM_INSTALL_LIB ON CACHE BOOL "Install ACSVM libraries.") +endif() + +## +## ACSVM_SHARED +## +## If true (or equiavalent), libraries will be built as SHARED. Otherwise, +## they are built as STATIC. +## +if(NOT DEFINED ACSVM_SHARED) + set(ACSVM_SHARED ${ACSVM_SHARED_DEFAULT} CACHE BOOL + "Build libraries as shared objects.") +endif() + +## +## ACSVM_SHARED_DECL +## +## Used internally for convenience in add_library commands. +## +if(ACSVM_SHARED) + set(ACSVM_SHARED_DECL SHARED) +else() + set(ACSVM_SHARED_DECL STATIC) +endif() + + +##----------------------------------------------------------------------------| +## Environment Configuration | +## + +include_directories(.) + + +##----------------------------------------------------------------------------| +## Targets | +## + +add_subdirectory(ACSVM) + +if(EXISTS "${CMAKE_SOURCE_DIR}/CAPI") + add_subdirectory(CAPI) +endif() + +if(EXISTS "${CMAKE_SOURCE_DIR}/Exec") + add_subdirectory(Exec) +endif() + +if(EXISTS "${CMAKE_SOURCE_DIR}/Util") + add_subdirectory(Util) +endif() + +## EOF + diff --git a/src/acs/vm/COPYING b/src/acs/vm/COPYING new file mode 100644 index 0000000000000000000000000000000000000000..4362b49151d7b34ef83b3067a8f9c9f877d72a0e --- /dev/null +++ b/src/acs/vm/COPYING @@ -0,0 +1,502 @@ + GNU LESSER GENERAL PUBLIC LICENSE + Version 2.1, February 1999 + + Copyright (C) 1991, 1999 Free Software Foundation, Inc. + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + +[This is the first released version of the Lesser GPL. It also counts + as the successor of the GNU Library Public License, version 2, hence + the version number 2.1.] + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +Licenses are intended to guarantee your freedom to share and change +free software--to make sure the software is free for all its users. + + This license, the Lesser General Public License, applies to some +specially designated software packages--typically libraries--of the +Free Software Foundation and other authors who decide to use it. You +can use it too, but we suggest you first think carefully about whether +this license or the ordinary General Public License is the better +strategy to use in any particular case, based on the explanations below. + + When we speak of free software, we are referring to freedom of use, +not price. Our General Public Licenses are designed to make sure that +you have the freedom to distribute copies of free software (and charge +for this service if you wish); that you receive source code or can get +it if you want it; that you can change the software and use pieces of +it in new free programs; and that you are informed that you can do +these things. + + To protect your rights, we need to make restrictions that forbid +distributors to deny you these rights or to ask you to surrender these +rights. These restrictions translate to certain responsibilities for +you if you distribute copies of the library or if you modify it. + + For example, if you distribute copies of the library, whether gratis +or for a fee, you must give the recipients all the rights that we gave +you. You must make sure that they, too, receive or can get the source +code. If you link other code with the library, you must provide +complete object files to the recipients, so that they can relink them +with the library after making changes to the library and recompiling +it. And you must show them these terms so they know their rights. + + We protect your rights with a two-step method: (1) we copyright the +library, and (2) we offer you this license, which gives you legal +permission to copy, distribute and/or modify the library. + + To protect each distributor, we want to make it very clear that +there is no warranty for the free library. Also, if the library is +modified by someone else and passed on, the recipients should know +that what they have is not the original version, so that the original +author's reputation will not be affected by problems that might be +introduced by others. + + Finally, software patents pose a constant threat to the existence of +any free program. We wish to make sure that a company cannot +effectively restrict the users of a free program by obtaining a +restrictive license from a patent holder. Therefore, we insist that +any patent license obtained for a version of the library must be +consistent with the full freedom of use specified in this license. + + Most GNU software, including some libraries, is covered by the +ordinary GNU General Public License. This license, the GNU Lesser +General Public License, applies to certain designated libraries, and +is quite different from the ordinary General Public License. We use +this license for certain libraries in order to permit linking those +libraries into non-free programs. + + When a program is linked with a library, whether statically or using +a shared library, the combination of the two is legally speaking a +combined work, a derivative of the original library. The ordinary +General Public License therefore permits such linking only if the +entire combination fits its criteria of freedom. The Lesser General +Public License permits more lax criteria for linking other code with +the library. + + We call this license the "Lesser" General Public License because it +does Less to protect the user's freedom than the ordinary General +Public License. It also provides other free software developers Less +of an advantage over competing non-free programs. These disadvantages +are the reason we use the ordinary General Public License for many +libraries. However, the Lesser license provides advantages in certain +special circumstances. + + For example, on rare occasions, there may be a special need to +encourage the widest possible use of a certain library, so that it becomes +a de-facto standard. To achieve this, non-free programs must be +allowed to use the library. A more frequent case is that a free +library does the same job as widely used non-free libraries. In this +case, there is little to gain by limiting the free library to free +software only, so we use the Lesser General Public License. + + In other cases, permission to use a particular library in non-free +programs enables a greater number of people to use a large body of +free software. For example, permission to use the GNU C Library in +non-free programs enables many more people to use the whole GNU +operating system, as well as its variant, the GNU/Linux operating +system. + + Although the Lesser General Public License is Less protective of the +users' freedom, it does ensure that the user of a program that is +linked with the Library has the freedom and the wherewithal to run +that program using a modified version of the Library. + + The precise terms and conditions for copying, distribution and +modification follow. Pay close attention to the difference between a +"work based on the library" and a "work that uses the library". The +former contains code derived from the library, whereas the latter must +be combined with the library in order to run. + + GNU LESSER GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License Agreement applies to any software library or other +program which contains a notice placed by the copyright holder or +other authorized party saying it may be distributed under the terms of +this Lesser General Public License (also called "this License"). +Each licensee is addressed as "you". + + A "library" means a collection of software functions and/or data +prepared so as to be conveniently linked with application programs +(which use some of those functions and data) to form executables. + + The "Library", below, refers to any such software library or work +which has been distributed under these terms. A "work based on the +Library" means either the Library or any derivative work under +copyright law: that is to say, a work containing the Library or a +portion of it, either verbatim or with modifications and/or translated +straightforwardly into another language. (Hereinafter, translation is +included without limitation in the term "modification".) + + "Source code" for a work means the preferred form of the work for +making modifications to it. For a library, complete source code means +all the source code for all modules it contains, plus any associated +interface definition files, plus the scripts used to control compilation +and installation of the library. + + Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running a program using the Library is not restricted, and output from +such a program is covered only if its contents constitute a work based +on the Library (independent of the use of the Library in a tool for +writing it). Whether that is true depends on what the Library does +and what the program that uses the Library does. + + 1. You may copy and distribute verbatim copies of the Library's +complete source code as you receive it, in any medium, provided that +you conspicuously and appropriately publish on each copy an +appropriate copyright notice and disclaimer of warranty; keep intact +all the notices that refer to this License and to the absence of any +warranty; and distribute a copy of this License along with the +Library. + + You may charge a fee for the physical act of transferring a copy, +and you may at your option offer warranty protection in exchange for a +fee. + + 2. You may modify your copy or copies of the Library or any portion +of it, thus forming a work based on the Library, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) The modified work must itself be a software library. + + b) You must cause the files modified to carry prominent notices + stating that you changed the files and the date of any change. + + c) You must cause the whole of the work to be licensed at no + charge to all third parties under the terms of this License. + + d) If a facility in the modified Library refers to a function or a + table of data to be supplied by an application program that uses + the facility, other than as an argument passed when the facility + is invoked, then you must make a good faith effort to ensure that, + in the event an application does not supply such function or + table, the facility still operates, and performs whatever part of + its purpose remains meaningful. + + (For example, a function in a library to compute square roots has + a purpose that is entirely well-defined independent of the + application. Therefore, Subsection 2d requires that any + application-supplied function or table used by this function must + be optional: if the application does not supply it, the square + root function must still compute square roots.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Library, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Library, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote +it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Library. + +In addition, mere aggregation of another work not based on the Library +with the Library (or with a work based on the Library) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may opt to apply the terms of the ordinary GNU General Public +License instead of this License to a given copy of the Library. To do +this, you must alter all the notices that refer to this License, so +that they refer to the ordinary GNU General Public License, version 2, +instead of to this License. (If a newer version than version 2 of the +ordinary GNU General Public License has appeared, then you can specify +that version instead if you wish.) Do not make any other change in +these notices. + + Once this change is made in a given copy, it is irreversible for +that copy, so the ordinary GNU General Public License applies to all +subsequent copies and derivative works made from that copy. + + This option is useful when you wish to copy part of the code of +the Library into a program that is not a library. + + 4. You may copy and distribute the Library (or a portion or +derivative of it, under Section 2) in object code or executable form +under the terms of Sections 1 and 2 above provided that you accompany +it with the complete corresponding machine-readable source code, which +must be distributed under the terms of Sections 1 and 2 above on a +medium customarily used for software interchange. + + If distribution of object code is made by offering access to copy +from a designated place, then offering equivalent access to copy the +source code from the same place satisfies the requirement to +distribute the source code, even though third parties are not +compelled to copy the source along with the object code. + + 5. A program that contains no derivative of any portion of the +Library, but is designed to work with the Library by being compiled or +linked with it, is called a "work that uses the Library". Such a +work, in isolation, is not a derivative work of the Library, and +therefore falls outside the scope of this License. + + However, linking a "work that uses the Library" with the Library +creates an executable that is a derivative of the Library (because it +contains portions of the Library), rather than a "work that uses the +library". The executable is therefore covered by this License. +Section 6 states terms for distribution of such executables. + + When a "work that uses the Library" uses material from a header file +that is part of the Library, the object code for the work may be a +derivative work of the Library even though the source code is not. +Whether this is true is especially significant if the work can be +linked without the Library, or if the work is itself a library. The +threshold for this to be true is not precisely defined by law. + + If such an object file uses only numerical parameters, data +structure layouts and accessors, and small macros and small inline +functions (ten lines or less in length), then the use of the object +file is unrestricted, regardless of whether it is legally a derivative +work. (Executables containing this object code plus portions of the +Library will still fall under Section 6.) + + Otherwise, if the work is a derivative of the Library, you may +distribute the object code for the work under the terms of Section 6. +Any executables containing that work also fall under Section 6, +whether or not they are linked directly with the Library itself. + + 6. As an exception to the Sections above, you may also combine or +link a "work that uses the Library" with the Library to produce a +work containing portions of the Library, and distribute that work +under terms of your choice, provided that the terms permit +modification of the work for the customer's own use and reverse +engineering for debugging such modifications. + + You must give prominent notice with each copy of the work that the +Library is used in it and that the Library and its use are covered by +this License. You must supply a copy of this License. If the work +during execution displays copyright notices, you must include the +copyright notice for the Library among them, as well as a reference +directing the user to the copy of this License. Also, you must do one +of these things: + + a) Accompany the work with the complete corresponding + machine-readable source code for the Library including whatever + changes were used in the work (which must be distributed under + Sections 1 and 2 above); and, if the work is an executable linked + with the Library, with the complete machine-readable "work that + uses the Library", as object code and/or source code, so that the + user can modify the Library and then relink to produce a modified + executable containing the modified Library. (It is understood + that the user who changes the contents of definitions files in the + Library will not necessarily be able to recompile the application + to use the modified definitions.) + + b) Use a suitable shared library mechanism for linking with the + Library. A suitable mechanism is one that (1) uses at run time a + copy of the library already present on the user's computer system, + rather than copying library functions into the executable, and (2) + will operate properly with a modified version of the library, if + the user installs one, as long as the modified version is + interface-compatible with the version that the work was made with. + + c) Accompany the work with a written offer, valid for at + least three years, to give the same user the materials + specified in Subsection 6a, above, for a charge no more + than the cost of performing this distribution. + + d) If distribution of the work is made by offering access to copy + from a designated place, offer equivalent access to copy the above + specified materials from the same place. + + e) Verify that the user has already received a copy of these + materials or that you have already sent this user a copy. + + For an executable, the required form of the "work that uses the +Library" must include any data and utility programs needed for +reproducing the executable from it. However, as a special exception, +the materials to be distributed need not include anything that is +normally distributed (in either source or binary form) with the major +components (compiler, kernel, and so on) of the operating system on +which the executable runs, unless that component itself accompanies +the executable. + + It may happen that this requirement contradicts the license +restrictions of other proprietary libraries that do not normally +accompany the operating system. Such a contradiction means you cannot +use both them and the Library together in an executable that you +distribute. + + 7. You may place library facilities that are a work based on the +Library side-by-side in a single library together with other library +facilities not covered by this License, and distribute such a combined +library, provided that the separate distribution of the work based on +the Library and of the other library facilities is otherwise +permitted, and provided that you do these two things: + + a) Accompany the combined library with a copy of the same work + based on the Library, uncombined with any other library + facilities. This must be distributed under the terms of the + Sections above. + + b) Give prominent notice with the combined library of the fact + that part of it is a work based on the Library, and explaining + where to find the accompanying uncombined form of the same work. + + 8. You may not copy, modify, sublicense, link with, or distribute +the Library except as expressly provided under this License. Any +attempt otherwise to copy, modify, sublicense, link with, or +distribute the Library is void, and will automatically terminate your +rights under this License. However, parties who have received copies, +or rights, from you under this License will not have their licenses +terminated so long as such parties remain in full compliance. + + 9. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Library or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Library (or any work based on the +Library), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Library or works based on it. + + 10. Each time you redistribute the Library (or any work based on the +Library), the recipient automatically receives a license from the +original licensor to copy, distribute, link with or modify the Library +subject to these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties with +this License. + + 11. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Library at all. For example, if a patent +license would not permit royalty-free redistribution of the Library by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Library. + +If any portion of this section is held invalid or unenforceable under any +particular circumstance, the balance of the section is intended to apply, +and the section as a whole is intended to apply in other circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 12. If the distribution and/or use of the Library is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Library under this License may add +an explicit geographical distribution limitation excluding those countries, +so that distribution is permitted only in or among countries not thus +excluded. In such case, this License incorporates the limitation as if +written in the body of this License. + + 13. The Free Software Foundation may publish revised and/or new +versions of the Lesser General Public License from time to time. +Such new versions will be similar in spirit to the present version, +but may differ in detail to address new problems or concerns. + +Each version is given a distinguishing version number. If the Library +specifies a version number of this License which applies to it and +"any later version", you have the option of following the terms and +conditions either of that version or of any later version published by +the Free Software Foundation. If the Library does not specify a +license version number, you may choose any version ever published by +the Free Software Foundation. + + 14. If you wish to incorporate parts of the Library into other free +programs whose distribution conditions are incompatible with these, +write to the author to ask for permission. For software which is +copyrighted by the Free Software Foundation, write to the Free +Software Foundation; we sometimes make exceptions for this. Our +decision will be guided by the two goals of preserving the free status +of all derivatives of our free software and of promoting the sharing +and reuse of software generally. + + NO WARRANTY + + 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO +WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. +EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR +OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY +KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE +LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME +THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN +WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY +AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU +FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR +CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE +LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING +RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A +FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF +SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH +DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Libraries + + If you develop a new library, and you want it to be of the greatest +possible use to the public, we recommend making it free software that +everyone can redistribute and change. You can do so by permitting +redistribution under these terms (or, alternatively, under the terms of the +ordinary General Public License). + + To apply these terms, attach the following notices to the library. It is +safest to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least the +"copyright" line and a pointer to where the full notice is found. + + <one line to give the library's name and a brief idea of what it does.> + Copyright (C) <year> <name of author> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + +Also add information on how to contact you by electronic and paper mail. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the library, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the + library `Frob' (a library for tweaking knobs) written by James Random Hacker. + + <signature of Ty Coon>, 1 April 1990 + Ty Coon, President of Vice + +That's all there is to it! diff --git a/src/acs/vm/README b/src/acs/vm/README new file mode 100644 index 0000000000000000000000000000000000000000..bb24555639c4cd2db9be2627d5f9ee7b5f34145b --- /dev/null +++ b/src/acs/vm/README @@ -0,0 +1,121 @@ +ACS Virtual Machine (ACSVM) + +ACS VM library and standalone interpreter. Intended to be suitable for use in +Doom-based video game engines to implement Hexen ACS or ZDoom ACS bytecode +execution. It is focused on being usable for implementing existing extensions +and new functions (whether through new instructions or CallFunc indexes) while +having performance suitable to the complex ACS-based mods that have come into +existence. + + +=============================================================================== +Integrating ACSVM +=============================================================================== + +Although it can be used as an external library, ACSVM is also written so that +it can be integrated directly into the repository of projects using it without +needing to change any of the ACSVM files. + +It is enough to copy ACSVM's root into the root of the target repository, such +that acsvm/CMakeLists.txt exists. Only the subdirectories of desired components +(usually just ACSVM) need to be included. The CMakeLists.txt will automatically +disable the omitted components. + +In your root CMakeLists.txt, all that is needed is: + set(ACSVM_NOFLAGS ON) + set(ACSVM_SHARED OFF) + add_subdirectory(acsvm) +And the enabled components (again, usually just acsvm) will be available for +use in target_link_libraries. + + +=============================================================================== +Usage Overview +=============================================================================== + +=========================================================== +Getting Started +=========================================================== + +To use ACSVM, you will need to define a class that inherits from +ACSVM::Environment. By overriding the various virtuals you can configure the +different aspects of ACS loading and interpretation. But the absolute minimal +usage only requires overriding loadModule: + class Env : public ACSVM::Environment + { + protected: + virtual void loadModule(ACSVM::Module *module); + }; + +Which is implemented by using the module's name to locate the corresponding +bytecode and passing that to module->readBytecode. The default behavior of +getModuleName is to just set the ModuleName's string. This can be used to +implement bytecode directly from files: + void Env::loadModule(ACSVM::Module *module) + { + std::ifstream in{module->name.s->str, + std::ios_base::in | std::ios_base::binary}; + + if(!in) throw ACSVM::ReadError("file open failure"); + + std::vector<ACSVM::Byte> data; + + for(int c; c = in.get(), in;) + data.push_back(c); + + module->readBytecode(data.data(), data.size()); + } +In a Doom engine, this would most likely use lumps, instead. Either by doing +the lookup in loadModule, or by overriding getModuleName to turn the input +string into a lump number. + +To actually initialize the environment and load some modules, you can use: + void EnvInit(Environment &env, char const *const *namev, std::size_t namec) + { + // Load modules. + std::vector<ACSVM::Module *> modules; + for(std::size_t i = 1; i < namec; ++i) + modules.push_back(env.getModule(env.getModuleName(namev[i]))); + + // Create and activate scopes. + ACSVM::GlobalScope *global = env.getGlobalScope(0); global->active = true; + ACSVM::HubScope *hub = global->getHubScope(0); hub ->active = true; + ACSVM::MapScope *map = hub->getMapScope(0); map ->active = true; + + // Register modules with map scope. + map->addModules(modules.data(), modules.size()); + + // Start Open scripts. + map->scriptStartType(1, {}); + } + +And then a simple interpreter loop: + while(env.hasActiveThread()) + { + std::chrono::duration<double> rate{1.0 / 35}; + auto time = std::chrono::steady_clock::now() + rate; + + env.exec(); + + std::this_thread::sleep_until(time); + } +Note that if you already have a game loop, you only need to call env.exec once +per simulation frame. + +Finally, you will need to register instruction and callfunc functions to +actually interface with the larger environment. At the least, it is useful to +implement the EndPrint (86) instruction: + bool CF_EndPrint(ACSVM::Thread *thread, ACSVM::Word const *, ACSVM::Word) + { + std::cout << thread->printBuf.data() << '\n'; + thread->printBuf.drop(); + return false; + } + + Environment::Environment() + { + addCodeDataACS0(86, {"", 0, addCallFunc(CF_EndPrint)}); + } +Most of the other ACS printing logic is already handled by ACSVM, so this is +enough to display simple Print messages. + diff --git a/src/acs/vm/Util/CMakeLists.txt b/src/acs/vm/Util/CMakeLists.txt new file mode 100644 index 0000000000000000000000000000000000000000..081e078d2c053ccfaf5482e6e50f78c16804342b --- /dev/null +++ b/src/acs/vm/Util/CMakeLists.txt @@ -0,0 +1,38 @@ +##----------------------------------------------------------------------------- +## +## Copyright (C) 2015 David Hill +## +## See COPYING for license information. +## +##----------------------------------------------------------------------------- +## +## CMake file for acsvm-util. +## +##----------------------------------------------------------------------------- + + +##----------------------------------------------------------------------------| +## Environment Configuration | +## + +include_directories(.) + + +##----------------------------------------------------------------------------| +## Targets | +## + +## +## acsvm-capi +## +add_library(acsvm-util ${ACSVM_SHARED_DECL} + Floats.cpp + Floats.hpp +) + +target_link_libraries(acsvm-util acsvm) + +ACSVM_INSTALL_LIB(acsvm-util) + +## EOF + diff --git a/src/acs/vm/Util/Floats.cpp b/src/acs/vm/Util/Floats.cpp new file mode 100644 index 0000000000000000000000000000000000000000..7436e6ea4eb39b1b9ace76fce436d36271364015 --- /dev/null +++ b/src/acs/vm/Util/Floats.cpp @@ -0,0 +1,89 @@ +//---------------------------------------------------------------------------- +// +// Copyright (C) 2015 David Hill +// +// See COPYING for license information. +// +//---------------------------------------------------------------------------- +// +// Floating-point utilities. +// +//---------------------------------------------------------------------------- + +#include "Floats.hpp" + +#include "ACSVM/Thread.hpp" + +#include <cstdio> + + +//----------------------------------------------------------------------------| +// Macros | +// + +// +// DoubleOp +// +#define DoubleOp(name, op) \ + bool CF_##name##F_W2(Thread *thread, Word const *argV, Word) \ + { \ + double l = WordsToFloat<double, 2>({{argV[0], argV[1]}}); \ + double r = WordsToFloat<double, 2>({{argV[2], argV[3]}}); \ + for(auto w : FloatToWords<2>(l op r)) thread->dataStk.push(w); \ + return false; \ + } + +// +// FloatOp +// +#define FloatOp(name, op) \ + bool CF_##name##F_W1(Thread *thread, Word const *argV, Word) \ + { \ + float l = WordsToFloat<float, 1>({{argV[0]}}); \ + float r = WordsToFloat<float, 1>({{argV[1]}}); \ + for(auto w : FloatToWords<1>(l op r)) thread->dataStk.push(w); \ + return false; \ + } + + +//----------------------------------------------------------------------------| +// Extern Functions | +// + +namespace ACSVM +{ + FloatOp(Add, +); + FloatOp(Div, /); + FloatOp(Mul, *); + FloatOp(Sub, -); + + DoubleOp(Add, +); + DoubleOp(Div, /); + DoubleOp(Mul, *); + DoubleOp(Sub, -); + + // + // void PrintDouble(double f) + // + bool CF_PrintDouble(Thread *thread, Word const *argV, Word) + { + double f = WordsToFloat<double, 2>({{argV[0], argV[1]}}); + thread->printBuf.reserve(std::snprintf(nullptr, 0, "%f", f)); + thread->printBuf.format("%f", f); + return false; + } + + // + // void PrintFloat(float f) + // + bool CF_PrintFloat(Thread *thread, Word const *argV, Word) + { + float f = WordsToFloat<float, 1>({{argV[0]}}); + thread->printBuf.reserve(std::snprintf(nullptr, 0, "%f", f)); + thread->printBuf.format("%f", f); + return false; + } +} + +// EOF + diff --git a/src/acs/vm/Util/Floats.hpp b/src/acs/vm/Util/Floats.hpp new file mode 100644 index 0000000000000000000000000000000000000000..94cd9b25ebb6b3a1a82af7d9aec6cc3413315442 --- /dev/null +++ b/src/acs/vm/Util/Floats.hpp @@ -0,0 +1,186 @@ +//---------------------------------------------------------------------------- +// +// Copyright (C) 2015 David Hill +// +// See COPYING for license information. +// +//---------------------------------------------------------------------------- +// +// Floating-point utilities. +// +//---------------------------------------------------------------------------- + +#ifndef ACSVM__Util__Floats_H__ +#define ACSVM__Util__Floats_H__ + +#include "../ACSVM/Types.hpp" + +#include <array> +#include <cmath> + + +//----------------------------------------------------------------------------| +// Extern Functions | +// + +namespace ACSVM +{ + bool CF_AddF_W1(Thread *thread, Word const *argV, Word argC); + bool CF_DivF_W1(Thread *thread, Word const *argV, Word argC); + bool CF_MulF_W1(Thread *thread, Word const *argV, Word argC); + bool CF_SubF_W1(Thread *thread, Word const *argV, Word argC); + bool CF_AddF_W2(Thread *thread, Word const *argV, Word argC); + bool CF_DivF_W2(Thread *thread, Word const *argV, Word argC); + bool CF_MulF_W2(Thread *thread, Word const *argV, Word argC); + bool CF_SubF_W2(Thread *thread, Word const *argV, Word argC); + + bool CF_PrintDouble(Thread *thread, Word const *argV, Word argC); + bool CF_PrintFloat(Thread *thread, Word const *argV, Word argC); + + // + // FloatExpBitDefault + // + template<std::size_t N> + constexpr std::size_t FloatExpBitDefault(); + + template<> constexpr std::size_t FloatExpBitDefault<1>() {return 8;} + template<> constexpr std::size_t FloatExpBitDefault<2>() {return 11;} + template<> constexpr std::size_t FloatExpBitDefault<4>() {return 15;} + + // + // FloatToWords + // + template<std::size_t N, std::size_t ExpBit = FloatExpBitDefault<N>(), typename FltT> + std::array<Word, N> FloatToWords(FltT const &f) + { + static_assert(N >= 1, "N must be at least 1."); + static_assert(ExpBit >= 2, "ExpBit must be at least 2."); + static_assert(ExpBit <= 30, "ExpBit must be at most 30."); + + + constexpr int ExpMax = (1 << (ExpBit - 1)) - 1; + constexpr int ExpMin = -ExpMax - 1; + constexpr int ExpOff = ExpMax - 1; + + + std::array<Word, N> w{}; + Word &wHi = std::get<N - 1>(w); + Word &wLo = std::get<0>(w); + + // Convert sign. + bool sigRaw = std::signbit(f); + wHi = static_cast<Word>(sigRaw) << 31; + + // Convert special values. + switch(std::fpclassify(f)) + { + case FP_INFINITE: + // Convert to +/-INFINITY. + wHi |= (0xFFFFFFFFu << (31 - ExpBit)) & 0x7FFFFFFF; + return w; + + case FP_NAN: + // Convert to NAN. + wHi |= 0x7FFFFFFF; + for(auto itr = &wLo, end = &wHi; itr != end; ++itr) + *itr = 0xFFFFFFFF; + return w; + + case FP_SUBNORMAL: + // TODO: Subnormals. + case FP_ZERO: + // Convert to +/-0. + return w; + } + + int expRaw = 0; + FltT manRaw = std::ldexp(std::frexp(std::fabs(f), &expRaw), N * 32 - ExpBit); + + // Check for exponent overflow. + if(expRaw > ExpMax) + { + // Overflow to +/-INFINITY. + wHi |= (0xFFFFFFFFu << (32 - ExpBit)) & 0x7FFFFFFF; + return w; + } + + // Check for exponent underflow. + if(expRaw < ExpMin) + { + // Underflow to +/-0. + return w; + } + + // Convert exponent. + wHi |= static_cast<Word>(expRaw + ExpOff) << (32 - ExpBit - 1); + + // Convert mantissa. + for(int i = 0, e = N - 1; i != e; ++i) + w[i] = static_cast<Word>(std::fmod(std::ldexp(manRaw, -i * 32), 4294967296.0)); + wHi |= static_cast<Word>(std::ldexp(manRaw, -static_cast<int>(N - 1) * 32)) + & ~(0xFFFFFFFFu << (31 - ExpBit)); + + return w; + } + + // + // WordsToFloat + // + template<typename FltT, std::size_t N, std::size_t ExpBit = FloatExpBitDefault<N>()> + FltT WordsToFloat(std::array<Word, N> const &w) + { + static_assert(N >= 1, "N must be at least 1."); + static_assert(ExpBit >= 2, "ExpBit must be at least 2."); + static_assert(ExpBit <= 30, "ExpBit must be at most 30."); + + + constexpr int ExpMax = (1 << (ExpBit - 1)) - 1; + constexpr int ExpMin = -ExpMax - 1; + constexpr int ExpOff = ExpMax; + + constexpr Word ManMask = 0xFFFFFFFFu >> (ExpBit + 1); + + + Word const &wHi = std::get<N - 1>(w); + Word const &wLo = std::get<0>(w); + + bool sig = !!(wHi & 0x80000000); + int exp = static_cast<int>((wHi & 0x7FFFFFFF) >> (31 - ExpBit)) - ExpOff; + + // INFINITY or NAN. + if(exp > ExpMax) + { + // Check for NAN. + for(auto itr = &wLo, end = &wHi; itr != end; ++itr) + if(*itr) return NAN; + if(wHi & ManMask) return NAN; + + return sig ? -INFINITY : +INFINITY; + } + + // Zero or subnormal. + if(exp < ExpMin) + { + // TODO: Subnormals. + + return sig ? -0.0f : +0.0f; + } + + // Convert mantissa. + FltT f = 0; + for(auto itr = &wHi, end = &wLo; itr != end;) + f = ldexp(f + *--itr, -32); + f = ldexp(f + (wHi & ManMask) + (ManMask + 1), -static_cast<int>(31 - ExpBit)); + + // Convert exponent. + f = ldexp(f, exp); + + // Convert sign. + f = sig ? -f : +f; + + return f; + } +} + +#endif//ACSVM__Util__Floats_H__ + diff --git a/src/acs/vm/doc/ACSVM.txt b/src/acs/vm/doc/ACSVM.txt new file mode 100644 index 0000000000000000000000000000000000000000..17837859be145e38bc0e15f07dff41c1865ec0a9 --- /dev/null +++ b/src/acs/vm/doc/ACSVM.txt @@ -0,0 +1,1212 @@ +############################################################################### +ACSVM Library Specification +############################################################################### + +=============================================================================== +Types <ACSVM/ACSVM/Types.hpp> +=============================================================================== + +Synopsis: + using Byte = unsigned char; + + using DWord = std::uint64_t; + using SDWord = std::int64_t; + using SWord = std::int32_t; + using Word = std::uint32_t; + + using CallFunc = bool (*)(Thread *thread, Word const *argv, Word argc); + +=============================================================================== +Deferred Actions <ACSVM/ACSVM/Action.hpp> +=============================================================================== + +=========================================================== +ACSVM::ScopeID +=========================================================== + +Synopsis: + #include <ACSVM/ACSVM/Action.hpp> + class ScopeID + { + public: + ScopeID() = default; + ScopeID(Word global, Word hub, Word map); + + bool operator == (ScopeID const &id) const; + bool operator != (ScopeID const &id) const; + + Word global; + Word hub; + Word map; + }; + +=============================================================================== +Arrays <ACSVM/ACSVM/Array.hpp> +=============================================================================== + +=========================================================== +ACSVM::Array +=========================================================== + +Synopsis: + #include <ACSVM/ACSVM/Array.hpp> + class Array + { + public: + Array(); + Array(Array const &) = delete; + Array(Array &&array); + ~Array(); + + Word &operator [] (Word idx); + + void clear(); + + Word find(Word idx) const; + + void lockStrings(Environment *env) const; + + void unlockStrings(Environment *env) const; + }; + +=============================================================================== +Codes <ACSVM/ACSVM/Code.hpp> +=============================================================================== + +=========================================================== +ACSVM::Code +=========================================================== + +Synopsis: + #include <ACSVM/ACSVM/Code.hpp> + enum class Code + { + /* ... */ + None + }; + +=========================================================== +ACSVM::Func +=========================================================== + +Synopsis: + #include <ACSVM/ACSVM/Code.hpp> + enum class Func + { + /* ... */ + None + }; + +=========================================================== +ACSVM::KillType +=========================================================== + +Synopsis: + #include <ACSVM/ACSVM/Code.hpp> + enum class KillType + { + None, + OutOfBounds, + UnknownCode, + UnknownFunc, + BranchLimit, + }; + +=============================================================================== +Code Data <ACSVM/ACSVM/CodeData.hpp> +=============================================================================== + +=========================================================== +ACSVM::CodeDataACS0 +=========================================================== + +Synopsis: + #include <ACSVM/ACSVM/CodeData.hpp> + class CodeDataACS0 + { + public: + CodeDataACS0(char const *args, Code transCode, Word stackArgC, + Word transFunc = 0); + CodeDataACS0(char const *args, Word stackArgC, Word transFunc); + + char const *args; + std::size_t argc; + + Word stackArgC; + + Code transCode; + + Word transFunc; + }; + +=========================================================== +ACSVM::FuncDataACS0 +=========================================================== + +Synopsis: + #include <ACSVM/ACSVM/CodeData.hpp> + class FuncDataACS0 + { + public: + using TransCode = std::pair<Word, Code>; + + + FuncDataACS0(FuncDataACS0 const &data); + FuncDataACS0(FuncDataACS0 &&data); + FuncDataACS0(Word transFunc); + FuncDataACS0(Word transFunc, std::initializer_list<TransCode> transCodes); + ~FuncDataACS0(); + + FuncDataACS0 &operator = (FuncDataACS0 const &) = delete; + FuncDataACS0 &operator = (FuncDataACS0 &&data); + + Word transFunc; + }; + +=============================================================================== +Environment <ACSVM/ACSVM/Environment.hpp> +=============================================================================== + +=========================================================== +ACSVM::Environment +=========================================================== + +Synopsis: + #include <ACSVM/ACSVM/Environment.hpp> + class Environment + { + public: + Environment(); + virtual ~Environment(); + + Word addCallFunc(CallFunc func); + + void addCodeDataACS0(Word code, CodeDataACS0 &&data); + + void addFuncDataACS0(Word func, FuncDataACS0 &&data); + + virtual bool checkLock(Thread *thread, Word lock, bool door); + + virtual bool checkTag(Word type, Word tag); + + void collectStrings(); + + virtual void exec(); + + void freeGlobalScope(GlobalScope *scope); + + void freeModule(Module *module); + + GlobalScope *getGlobalScope(Word id); + + Module *getModule(ModuleName const &name); + + ModuleName getModuleName(char const *str); + virtual ModuleName getModuleName(char const *str, std::size_t len); + + String *getString(Word idx); + String *getString(char const *first, char const *last); + String *getString(char const *str); + String *getString(char const *str, std::size_t len); + String *getString(StringData const *data); + + bool hasActiveThread() const; + + virtual void loadState(Serial &in); + + virtual void printArray(PrintBuf &buf, Array const &array, Word index, + Word limit); + + virtual void printKill(Thread *thread, Word type, Word data); + + virtual ModuleName readModuleName(Serial &in) const; + + String *readString(Serial &in) const; + + virtual void refStrings(); + + virtual void resetStrings(); + + virtual void saveState(Serial &out) const; + + virtual void writeModuleName(Serial &out, ModuleName const &name) const; + + void writeString(Serial &out, String const *in) const; + + StringTable stringTable; + + Word scriptLocRegC; + + + static void PrintArrayChar(PrintBuf &buf, Array const &array, Word index, + Word limit); + + static void PrintArrayUTF8(PrintBuf &buf, Array const &array, Word index, + Word limit); + + static constexpr Word ScriptLocRegCDefault; + + protected: + virtual Thread *allocThread(); + + virtual Word callSpecImpl(Thread *thread, Word spec, Word const *argV, + Word argC); + + virtual void loadModule(Module *module) = 0; + }; + +Description: + Represents an execution environment. + +----------------------------------------------------------- +ACSVM::Environment::Environment +----------------------------------------------------------- + +Synopsis: + Environment(); + +Description: + Constructs the Environment object. + +----------------------------------------------------------- +ACSVM::Environment::~Environment +----------------------------------------------------------- + +Synopsis: + ~Environment(); + +Description: + Destructs the Environment object. + +----------------------------------------------------------- +ACSVM::Environment::addCallFunc +----------------------------------------------------------- + +Synopsis: + Word addCallFunc(CallFunc func); + +Description: + Adds a function callback for scripts and returns its index. + +Returns: + Added function's index. + +----------------------------------------------------------- +ACSVM::Environment::addCodeDataACS0 +----------------------------------------------------------- + +Synopsis: + void addCodeDataACS0(Word code, CodeDataACS0 &&data); + +Description: + Adds a translation from instruction code for ACS0 and derived bytecode. + +----------------------------------------------------------- +ACSVM::Environment::addFuncDataACS0 +----------------------------------------------------------- + +Synopsis: + void addFuncDataACS0(Word func, FuncDataACS0 &&data); + +Description: + Adds a translation from callfunc func for ACS0 and derived bytecode. + +----------------------------------------------------------- +ACSVM::Environment::checkLock +----------------------------------------------------------- + +Synopsis: + virtual bool checkLock(Thread *thread, Word lock, bool door); + +Description: + Called to check if a given lock number can be used from a given thread. The + lock number has no internal semantics, and is passed from the user source + unaltered. + + The base implementation always return false. + +Returns: + True if the lock is open for that thread, false otherwise. + +----------------------------------------------------------- +ACSVM::Environment::checkTag +----------------------------------------------------------- + +Synopsis: + virtual bool checkTag(Word type, Word tag); + +Description: + Called to check if a given tag is inactive. The tag type and number both have + no internal semantics, and are passed from the user source unaltered. + + The base implementation always returns false. + +Returns: + True if the tag is inactive, false otherwise. + +----------------------------------------------------------- +ACSVM::Environment::collectStrings +----------------------------------------------------------- + +Synopsis: + void collectStrings(); + +Description: + Performs a full scan of the environment and frees strings that are no longer + in use. + +----------------------------------------------------------- +ACSVM::Environment::exec +----------------------------------------------------------- + +Synopsis: + virtual void exec(); + +Description: + Performs a single execution cycle. Deferred script actions will be applied, + and active threads will execute until they terminate or enter a wait state. + +----------------------------------------------------------- +ACSVM::Environment::freeGlobalScope +----------------------------------------------------------- + +Synopsis: + void freeGlobalScope(GlobalScope *scope); + +Description: + Destructs and deallocates a contained GlobalScope object. + +----------------------------------------------------------- +ACSVM::Environment::freeModule +----------------------------------------------------------- + +Synopsis: + void freeModule(Module *module); + +Description: + Destructs and deallocates a contained Module object. + + If any other modules reference the freed module, they must also be freed. + +----------------------------------------------------------- +ACSVM::Environment::getGlobalScope +----------------------------------------------------------- + +Synopsis: + GlobalScope *getGlobalScope(Word id); + +Description: + Retrieves a GlobalScope object by its identifier number. If it does not + exist, it will be created. + +Returns: + GlobalScope object with given id. + +----------------------------------------------------------- +ACSVM::Environment::getModule +----------------------------------------------------------- + +Synopsis: + Module *getModule(ModuleName const &name); + +Description: + Retrieves a Module object by name. If it does not exist or is not loaded, it + will be created and loaded as needed. + +Returns: + Module object with given name. + +----------------------------------------------------------- +ACSVM::Environment::getModuleName +----------------------------------------------------------- + +Synopsis: + ModuleName getModuleName(char const *str); + virtual ModuleName getModuleName(char const *str, std::size_t len); + +Description: + Generates a ModuleName from an input string. The first form calls the second, + using the null-terminated length of the input string. + + The base implementation converts the input string into a String object for + ModuleName::s, leaving the other ModuleName fields set to 0. + +Returns: + ModuleName object formed from input string. + +----------------------------------------------------------- +ACSVM::Environment::getScriptType +----------------------------------------------------------- + +Synopsis: + virtual std::pair<Word, Word> getScriptTypeACS0(Word name); + virtual Word getScriptTypeACSE(Word type); + +Description: + Translates a bytecode script type into an internal type. + + First form takes the script number and must return the type and name. + + The base implementation of the first form translates by dividing by 1000. The + second form returns the type unaltered. + +Returns: + Translated script type or (type, name) pair. + +----------------------------------------------------------- +ACSVM::Environment::getString +----------------------------------------------------------- + +Synopsis: + String *getString(Word idx); + String *getString(char const *first, char const *last); + String *getString(char const *str); + String *getString(char const *str, std::size_t len); + String *getString(StringData const *data); + +Description: + First form returns a String object as if by calling (&stringTable[~idx]). + + Second, third, and fourth forms create a StringData from input to find or + create an entry in stringTable. + + Fifth form uses the supplied StringData object to find or create an entry in + stringTable. If data is null, null is returned. This is intended primarily + for resetting strings after deserialization. + +Returns: + First form returns the String object with the given index. All other forms + return a String object with the same data as the input. + + Fifth form will return null if input is null, and non-null otherwise. All + other forms never return null. + +----------------------------------------------------------- +ACSVM::Environment::hasActiveThread +----------------------------------------------------------- + +Synopsis: + bool hasActiveThread() const; + +Description: + Checks for any active threads. A thread is considered active if it has any + state other than ThreadState::Inactive. So this will include threads that are + delayed, waiting for a condition (script or tag), or set to stop during the + next execution cycle. + +Returns: + True if there are any active threads, false otherwise. + +----------------------------------------------------------- +ACSVM::Environment::loadState +----------------------------------------------------------- + +Synopsis: + virtual void loadState(Serial &in); + +Description: + Restores the environment state from a previously serialized instance. If in + does not contain a byte stream generated by a previous call to saveState, the + behavior is undefined. + +----------------------------------------------------------- +ACSVM::Environment::printArray +----------------------------------------------------------- + +Synopsis: + virtual void printArray(PrintBuf &buf, Array const &array, Word index, + Word limit); + +Description: + Called to write a null-terminated character subsequence from an Array object + to a print buffer. + + The base implementation calls PrintArrayChar. + +----------------------------------------------------------- +ACSVM::Environment::printKill +----------------------------------------------------------- + +Synopsis: + virtual void printKill(Thread *thread, Word type, Word data); + +Description: + Called when a thread encounters an error and must terminate. This includes + executing Code::Kill or calling Func::Kill. When calling by ACSVM itself, the + type parameter has a value from the KillType enumeration. + + This function is expected to return normally, and the caller will handle + thread termination. + + The base implementation prints kill information to std::cerr. + +----------------------------------------------------------- +ACSVM::Environment::readModuleName +----------------------------------------------------------- + +Synopsis: + virtual ModuleName readModuleName(Serial &in) const; + +Description: + Called to read a ModuleName from a serialized environment. + + The base implementation reads the s and i members, leaving the p member null. + +Returns: + Deserialized ModuleName. + +----------------------------------------------------------- +ACSVM::Environment::readString +----------------------------------------------------------- + +Synopsis: + String *readString(Serial &in) const; + +Description: + Reads a String by index from a serialized Environment. If the written String + pointer was null, this function returns a null pointer. + +Returns: + Deserialized String. + +----------------------------------------------------------- +ACSVM::Environment::refStrings +----------------------------------------------------------- + +Synopsis: + virtual void refStrings(); + +Description: + Called by collectStrings to mark contained strings as referenced. + + The base implementation marks strings of all contained objects, as well as + performs an exhaustive scan of VM memory for string indexes. + +----------------------------------------------------------- +ACSVM::Environment::resetStrings +----------------------------------------------------------- + +Synopsis: + virtual void resetStrings(); + +Description: + Called by loadState after reading the new StringTable to reset any String + pointers to the corresponding entry in the read table. + + The base implementation resets strings of all contained objects. + +----------------------------------------------------------- +ACSVM::Environment::saveState +----------------------------------------------------------- + +Synopsis: + virtual void saveState(Serial &out) const; + +Description: + Serializes the environment state, which can be restored with a call to + loadState. + +----------------------------------------------------------- +ACSVM::Environment::writeModuleName +----------------------------------------------------------- + +Synopsis: + virtual void writeModuleName(Serial &out, + ModuleName const &name) const; + +Description: + Called to write a ModuleName. + + The base implementation writes the s and i members. + +----------------------------------------------------------- +ACSVM::Environment::writeString +----------------------------------------------------------- + +Synopsis: + void writeString(Serial &out, String const *in) const; + +Description: + Writes a String by index. If in is null, a null pointer will be returned by + the corresponding call to readString. + +=============================================================================== +Errors <ACSVM/ACSVM/Error.hpp> +=============================================================================== + +=========================================================== +ACSVM::ReadError +=========================================================== + +Synopsis: + #include <ACSVM/ACSVM/Error.hpp> + class ReadError : public std::exception + { + public: + ReadError(char const *msg = "ACSVM::ReadError"); + + virtual char const *what() const noexcept; + }; + +=============================================================================== +Modules <ACSVM/ACSVM/Module.hpp> +=============================================================================== + +=========================================================== +ACSVM::ModuleName +=========================================================== + +Synopsis: + #include <ACSVM/ACSVM/Module.hpp> + class ModuleName + { + public: + ModuleName(String *s, void *p, std::size_t i); + + bool operator == (ModuleName const &name) const; + bool operator != (ModuleName const &name) const; + + std::size_t hash() const; + + String *s; + void *p; + std::size_t i; + }; + +=========================================================== +ACSVM::Module +=========================================================== + +Synopsis: + #include <ACSVM/ACSVM/Module.hpp> + class Module + { + public: + void readBytecode(Byte const *data, std::size_t size); + + Environment *env; + ModuleName name; + + bool isACS0; + bool loaded; + + + static constexpr std::uint32_t ChunkID(char c0, char c1, char c2, char c3); + + static constexpr std::uint32_t ChunkID(char const (&s)[5]); + }; + +=============================================================================== +Print Buffers <ACSVM/ACSVM/PrintBuf.hpp> +=============================================================================== + +=========================================================== +ACSVM::PrintBuf +=========================================================== + +Synopsis: + #include <ACSVM/ACSVM/PrintBuf.hpp> + class PrintBuf + { + public: + PrintBuf(); + ~PrintBuf(); + + void clear(); + + char const *data() const; + char const *dataFull() const; + + void drop(); + + void format(char const *fmt, ...); + void formatv(char const *fmt, std::va_list arg); + + char *getBuf(std::size_t count); + + char *getLoadBuf(std::size_t countFull, std::size_t count); + + void push(); + + void put(char c); + void put(char const *s); + void put(char const *s, std::size_t n); + + void reserve(std::size_t count); + + std::size_t size() const; + std::size_t sizeFull() const; + }; + +=============================================================================== +Scopes <ACSVM/ACSVM/Scope.hpp> +=============================================================================== + +=========================================================== +ACSVM::GlobalScope +=========================================================== + +Synopsis: + #include <ACSVM/ACSVM/Scope.hpp> + class GlobalScope + { + public: + static constexpr std::size_t ArrC = 256; + static constexpr std::size_t RegC = 256; + + + void freeHubScope(HubScope *scope); + + HubScope *getHubScope(Word id); + + bool hasActiveThread() const; + + void lockStrings() const; + + void reset(); + + void unlockStrings() const; + + Environment *const env; + Word const id; + + Array arrV[ArrC]; + Word regV[RegC]; + + bool active; + }; + +=========================================================== +ACSVM::HubScope +=========================================================== + +Synopsis: + #include <ACSVM/ACSVM/Scope.hpp> + class HubScope + { + public: + static constexpr std::size_t ArrC = 256; + static constexpr std::size_t RegC = 256; + + + void freeMapScope(MapScope *scope); + + MapScope *getMapScope(Word id); + + bool hasActiveThread() const; + + void lockStrings() const; + + void reset(); + + void unlockStrings() const; + + Environment *const env; + GlobalScope *const global; + Word const id; + + Array arrV[ArrC]; + Word regV[RegC]; + + bool active; + }; + +=========================================================== +ACSVM::MapScope +=========================================================== + +Synopsis: + #include <ACSVM/ACSVM/Scope.hpp> + class MapScope + { + public: + using ScriptStartFunc = void (*)(Thread *); + using ScriptStartFuncC = MapScope_ScriptStartFuncC; + + class ScriptStartInfo; + + + void addModules(Module *const *moduleV, std::size_t moduleC); + + Script *findScript(ScriptName name); + + ModuleScope *getModuleScope(Module *module); + + String *getString(Word idx) const; + + bool hasActiveThread() const; + + bool hasModules() const; + + bool isScriptActive(Script *script); + + void loadState(Serial &in); + + void lockStrings() const; + + void reset(); + + void saveState(Serial &out) const; + + bool scriptPause(Script *script); + bool scriptPause(ScriptName name, ScopeID scope); + bool scriptStart(Script *script, ScriptStartInfo const &info); + bool scriptStart(ScriptName name, ScopeID scope, + ScriptStartInfo const &info); + bool scriptStartForced(Script *script, ScriptStartInfo const &info); + bool scriptStartForced(ScriptName name, ScopeID scope, + ScriptStartInfo const &info); + Word scriptStartResult(Script *script, ScriptStartInfo const &info); + Word scriptStartResult(ScriptName name, ScriptStartInfo const &info); + Word scriptStartType(Word type, ScriptStartInfo const &info); + Word scriptStartTypeForced(Word type, ScriptStartInfo const &info); + bool scriptStop(Script *script); + bool scriptStop(ScriptName name, ScopeID scope); + + void unlockStrings() const; + + Environment *const env; + HubScope *const hub; + Word const id; + + bool active; + bool clampCallSpec; + }; + +=========================================================== +ACSVM::MapScope::ScriptStartInfo +=========================================================== + +Synopsis: + #include <ACSVM/ACSVM/Scope.hpp> + class ScriptStartInfo + { + public: + ScriptStartInfo(); + ScriptStartInfo(Word const *argV, std::size_t argC, + ThreadInfo const *info = nullptr, ScriptStartFunc func = nullptr); + ScriptStartInfo(Word const *argV, std::size_t argC, + ThreadInfo const *info, ScriptStartFuncC func); + + Word const *argV; + ScriptStartFunc func; + ScriptStartFuncC funcc; + ThreadInfo const *info; + std::size_t argC; + }; + +=========================================================== +ACSVM::ModuleScope +=========================================================== + +Synopsis: + #include <ACSVM/ACSVM/Scope.hpp> + class ModuleScope + { + public: + static constexpr std::size_t ArrC = 256; + static constexpr std::size_t RegC = 256; + + + void lockStrings() const; + + void unlockStrings() const; + + Environment *const env; + MapScope *const map; + Module *const module; + + Array *arrV[ArrC]; + Word *regV[RegC]; + }; + +=============================================================================== +Scripts <ACSVM/ACSVM/Script.hpp> +=============================================================================== + +=========================================================== +ACSVM::ScriptName +=========================================================== + +Synopsis: + #include <ACSVM/ACSVM/Script.hpp> + class ScriptName + { + public: + ScriptName(); + ScriptName(String *s); + ScriptName(String *s, Word i); + ScriptName(Word i); + + String *s; + Word i; + }; + +=========================================================== +ACSVM::Script +=========================================================== + +Synopsis: + #include <ACSVM/ACSVM/Script.hpp> + class Script + { + public: + explicit Script(Module *module); + ~Script(); + + Module *const module; + + ScriptName name; + + Word argC; + Word codeIdx; + Word flags; + Word locArrC; + Word locRegC; + Word type; + + bool flagClient : 1; + bool flagNet : 1; + }; + +=============================================================================== +Stacks <ACSVM/ACSVM/Stack.hpp> +=============================================================================== + +=========================================================== +ACSVM::Stack +=========================================================== + +Synopsis: + #include <ACSVM/ACSVM/Stack.hpp> + template<typename T> + class Stack + { + public: + Stack(); + ~Stack(); + + T &operator [] (std::size_t idx); + + T *begin(); + T const *begin() const; + + void clear(); + + void drop(); + void drop(std::size_t n); + + bool empty() const; + + T *end(); + T const *end() const; + + void push(T const &value); + void push(T &&value); + + void reserve(std::size_t count); + + std::size_t size() const; + }; + +=============================================================================== +Locals Storage <ACSVM/ACSVM/Store.hpp> +=============================================================================== + +=========================================================== +ACSVM::Store +=========================================================== + +Synopsis: + #include <ACSVM/ACSVM/Store.hpp> + template<typename T> + class Store + { + public: + Store(); + ~Store(); + + T &operator [] (std::size_t idx); + + void alloc(std::size_t count); + + void allocLoad(std::size_t countFull, std::size_t count); + + T *begin(); + T const *begin() const; + + T *beginFull(); + T const *beginFull() const; + + void clear(); + + T const *dataFull() const; + + T *end(); + T const *end() const; + + void free(std::size_t count); + + std::size_t size() const; + + std::size_t sizeFull() const; + }; + +=============================================================================== +Strings <ACSVM/ACSVM/String.hpp> +=============================================================================== + +=========================================================== +ACSVM::StringData +=========================================================== + +Synopsis: + #include <ACSVM/ACSVM/String.hpp> + class StringData + { + public: + StringData(char const *first, char const *last); + StringData(char const *str, std::size_t len); + StringData(char const *str, std::size_t len, std::size_t hash); + + bool operator == (StringData const &r) const; + + char const *const str; + std::size_t const len; + std::size_t const hash; + }; + +=========================================================== +ACSVM::String +=========================================================== + +Synopsis: + #include <ACSVM/ACSVM/String.hpp> + class String : public StringData + { + public: + std::size_t lock; + + Word const idx; + Word const len0; + + bool ref; + + char get(std::size_t i) const; + }; + +=========================================================== +ACSVM::StringTable +=========================================================== + +Synopsis: + #include <ACSVM/ACSVM/String.hpp> + class StringTable + { + public: + StringTable(); + StringTable(StringTable &&table); + ~StringTable(); + + String &operator [] (Word idx) const; + String &operator [] (StringData const &data); + + void clear(); + + void collectBegin(); + void collectEnd(); + + String &getNone(); + + std::size_t size() const; + }; + +=============================================================================== +Threads <ACSVM/ACSVM/Thread.hpp> +=============================================================================== + +=========================================================== +ACSVM::ThreadState +=========================================================== + +Synopsis: + #include <ACSVM/ACSVM/Thread.hpp> + class ThreadState + { + public: + enum State + { + Inactive, + Running, + Stopped, + Paused, + WaitScrI, + WaitScrS, + WaitTag, + }; + + + ThreadState(); + ThreadState(State state); + ThreadState(State state, Word data); + ThreadState(State state, Word data, Word type); + + bool operator == (State s) const; + bool operator != (State s) const; + + State state; + Word data; + Word type; + }; + +=========================================================== +ACSVM::ThreadInfo +=========================================================== + +Synopsis: + #include <ACSVM/ACSVM/Thread.hpp> + class ThreadInfo + { + public: + virtual ~ThreadInfo() {} + }; + +=========================================================== +ACSVM::Thread +=========================================================== + +Synopsis: + #include <ACSVM/ACSVM/Thread.hpp> + class Thread + { + public: + virtual ThreadInfo const *getInfo() const; + + virtual void lockStrings() const; + + virtual void unlockStrings() const; + + Environment *const env; + + Stack<CallFrame> callStk; + Stack<Word> dataStk; + Store<Array> localArr; + Store<Word> localReg; + PrintBuf printBuf; + ThreadState state; + + Word const *codePtr; + Module *module; + GlobalScope *scopeGbl; + HubScope *scopeHub; + MapScope *scopeMap; + ModuleScope *scopeMod; + Script *script; + Word delay; + Word result; + }; + +############################################################################### +EOF +############################################################################### + diff --git a/src/b_bot.c b/src/b_bot.c index af57d65ecf6e3c24e725ed38b9533b9ab6509763..483a9d0650c268569e098eb9440ccc0118732755 100644 --- a/src/b_bot.c +++ b/src/b_bot.c @@ -1,7 +1,7 @@ // SONIC ROBO BLAST 2 //----------------------------------------------------------------------------- // Copyright (C) 2007-2016 by John "JTE" Muniz. -// Copyright (C) 2011-2023 by Sonic Team Junior. +// Copyright (C) 2011-2024 by Sonic Team Junior. // // This program is free software distributed under the // terms of the GNU General Public License, version 2. @@ -14,11 +14,89 @@ #include "d_player.h" #include "g_game.h" #include "r_main.h" +#include "r_skins.h" +#include "hu_stuff.h" #include "p_local.h" #include "b_bot.h" #include "lua_hook.h" #include "i_system.h" // I_BaseTiccmd +INT16 B_AddBot(const char *skinname, UINT16 skincolor, const char *name, SINT8 type) +{ + INT16 i, newplayernum; + player_t *newplayer; + SINT8 skinnum = 0; + + for (i = 0; i < MAXPLAYERS; i++) + { + if (!playeringame[i]) + break; + } + + if (i >= MAXPLAYERS) + { + return -1; + } + + newplayernum = i; + + CL_ClearPlayer(newplayernum); + + playeringame[newplayernum] = true; + G_AddPlayer(newplayernum); + newplayer = &players[newplayernum]; + + newplayer->jointime = 0; + newplayer->quittime = 0; + newplayer->lastinputtime = 0; + + // Set the skin (defaults to Sonic) + if (skinname) + { + skinnum = R_SkinAvailable(skinname); + skinnum = skinnum < 0 ? 0 : skinnum; + } + + // Set the color (defaults to skin prefcolor) + if (skincolor == SKINCOLOR_NONE) + newplayer->skincolor = skins[skinnum]->prefcolor; + else + newplayer->skincolor = skincolor; + + // Set the bot default name as the skin + strcpy(player_names[newplayernum], skins[skinnum]->realname); + + // Read the bot name, if given + if (name != NULL) + strlcpy(player_names[newplayernum], name, sizeof(*player_names)); + + newplayer->bot = (type >= BOT_NONE && type <= BOT_MPAI) ? type : BOT_MPAI; + + // If our bot is a 2P type, we'll need to set its leader so it can spawn + if (newplayer->bot == BOT_2PAI || newplayer->bot == BOT_2PHUMAN) + B_UpdateBotleader(newplayer); + + // Set the skin (can't do this until AFTER bot type is set!) + SetPlayerSkinByNum(newplayernum, skinnum); + + if (netgame) + { + char joinmsg[256]; + + // Truncate bot name + player_names[newplayernum][sizeof(*player_names) - 8] = '\0'; // The length of colored [BOT] + 1 + + strcpy(joinmsg, M_GetText("\x82*Bot %s has joined the game (player %d)")); + strcpy(joinmsg, va(joinmsg, player_names[newplayernum], newplayernum)); + HU_AddChatText(joinmsg, false); + + // Append blue [BOT] tag at the end + strlcat(player_names[newplayernum], "\x84[BOT]\x80", sizeof(*player_names)); + } + + return newplayernum; +} + void B_UpdateBotleader(player_t *player) { UINT32 i; diff --git a/src/b_bot.h b/src/b_bot.h index bbe0829bea7274297dd3718d553aec9d4c23a9d6..1115e65eb65dd9b3217662866ea9c98563aca8b4 100644 --- a/src/b_bot.h +++ b/src/b_bot.h @@ -1,7 +1,7 @@ // SONIC ROBO BLAST 2 //----------------------------------------------------------------------------- // Copyright (C) 2007-2016 by John "JTE" Muniz. -// Copyright (C) 2012-2023 by Sonic Team Junior. +// Copyright (C) 2012-2024 by Sonic Team Junior. // // This program is free software distributed under the // terms of the GNU General Public License, version 2. @@ -10,6 +10,14 @@ /// \file b_bot.h /// \brief Basic bot handling +#ifndef __B_BOT__ +#define __B_BOT__ + +#ifdef __cplusplus +extern "C" { +#endif + +INT16 B_AddBot(const char *skinname, UINT16 skincolor, const char *name, SINT8 type); void B_UpdateBotleader(player_t *player); void B_BuildTiccmd(player_t *player, ticcmd_t *cmd); void B_KeysToTiccmd(mobj_t *mo, ticcmd_t *cmd, boolean forward, boolean backward, boolean left, boolean right, boolean strafeleft, boolean straferight, boolean jump, boolean spin); @@ -17,3 +25,9 @@ boolean B_CheckRespawn(player_t *player); void B_MoveBlocked(player_t *player); void B_RespawnBot(INT32 playernum); void B_HandleFlightIndicator(player_t *player); + +#ifdef __cplusplus +} // extern "C" +#endif + +#endif diff --git a/src/cxxutil.hpp b/src/cxxutil.hpp new file mode 100644 index 0000000000000000000000000000000000000000..d7405191419d50154de103c0ea1590608d91cb9a --- /dev/null +++ b/src/cxxutil.hpp @@ -0,0 +1,194 @@ +// SONIC ROBO BLAST 2 +//----------------------------------------------------------------------------- +// Copyright (C) 2024 by Ronald "Eidolon" Kinard +// Copyright (C) 2024 by Kart Krew +// +// This program is free software distributed under the +// terms of the GNU General Public License, version 2. +// See the 'LICENSE' file for more details. +//----------------------------------------------------------------------------- + +#ifndef __SRB2_CXXUTIL_HPP__ +#define __SRB2_CXXUTIL_HPP__ + +#include <cstdlib> +#include <functional> +#include <type_traits> +#include <utility> + +#include "doomdef.h" + +namespace srb2 { + +template <class F> +class Finally { +public: + explicit Finally(const F& f) noexcept : f_(f) {} + explicit Finally(F&& f) noexcept : f_(f) {} + + Finally(Finally&& from) noexcept : f_(std::move(from.f_)), call_(std::exchange(from.call_, false)) {} + + Finally(const Finally& from) = delete; + void operator=(const Finally& from) = delete; + void operator=(Finally&& from) = delete; + + ~Finally() noexcept { + if (call_) + f_(); + } + +private: + F f_; + bool call_ = true; +}; + +template <class F> +Finally<std::decay_t<F>> finally(F&& f) noexcept { + return Finally {std::forward<F>(f)}; +} + +struct SourceLocation { + const char* file_name; + unsigned int line_number; +}; + +#define SRB2_SOURCE_LOCATION \ + srb2::SourceLocation { __FILE__, __LINE__ } + +#ifndef SRB2_ASSERT_HANDLER +#define SRB2_ASSERT_HANDLER srb2::IErrorAssertHandler +#endif + +#if !defined(NDEBUG) || defined(PARANOIA) +// An assertion level of 2 will activate all invocations of the SRB2_ASSERT macro +#define SRB2_ASSERTION_LEVEL 2 +#else +// The minimum assertion level is 1 +#define SRB2_ASSERTION_LEVEL 1 +#endif + +/// Assert a precondition expression in debug builds. +#define SRB2_ASSERT(expr) srb2::do_assert<2, SRB2_ASSERT_HANDLER>([&] { return (expr); }, SRB2_SOURCE_LOCATION, #expr) + +class IErrorAssertHandler { +public: + static void handle(const SourceLocation& source_location, const char* expression) { + I_Error( + "Assertion failed at %s:%u: %s != true", + source_location.file_name, + source_location.line_number, + expression + ); + } +}; + +class NoOpAssertHandler { +public: + static void handle(const SourceLocation& source_location, const char* expression) { + (void)source_location; + (void)expression; + } +}; + +/// @brief Assert a precondition expression, aborting the application if it fails. +/// @tparam Expr +/// @tparam Level the level of this assertion; if it is less than or equal to SRB2_ASSERTION_LEVEL, this overload will +/// activate. +/// @param expr a callable which returns a bool +/// @param source_location a struct containing the source location of the assertion, e.g. SRB2_SOURCE_LOCATION +/// @param expression the expression evaluated in the expression callable +/// @param message an optional message to display for the assertion +template <unsigned int Level, class Handler, class Expr> +std::enable_if_t<(Level <= SRB2_ASSERTION_LEVEL), void> +do_assert(const Expr& expr, const SourceLocation& source_location, const char* expression = "") noexcept { + static_assert(Level > 0, "level of an assertion must not be 0"); + if (!expr()) { + Handler::handle(source_location, expression); + std::abort(); + } +} + +template <unsigned int Level, class, class Expr> +std::enable_if_t<(Level > SRB2_ASSERTION_LEVEL), void> +do_assert(const Expr&, const SourceLocation&, const char* = "") noexcept { +} + +template <typename T> +class NotNull final { + T ptr_; + +public: + static_assert( + std::is_convertible_v<decltype(std::declval<T>() != nullptr), bool>, + "T is not comparable with nullptr_t" + ); + + /// @brief Move-construct from the pointer value U, asserting that it is not null. Allows construction of a + /// NotNull<T> from any compatible pointer U, for example with polymorphic classes. + template <typename U, typename = std::enable_if_t<std::is_convertible_v<U, T>>> + constexpr NotNull(U&& rhs) : ptr_(std::forward<U>(rhs)) { + SRB2_ASSERT(ptr_ != nullptr); + } + + /// @brief Wrap the pointer type T, asserting that the pointer is not null. + template <typename = std::enable_if_t<!std::is_same_v<std::nullptr_t, T>>> + constexpr NotNull(T rhs) : ptr_(std::move(rhs)) { + SRB2_ASSERT(ptr_ != nullptr); + } + + /// @brief Copy construction from NotNull of convertible type U. Only if the incoming pointer is NotNull already. + template <typename U, typename = std::enable_if_t<std::is_convertible_v<U, T>>> + constexpr NotNull(const NotNull<U>& rhs) : NotNull(rhs.get()) { + // Value is guaranteed to be not null by construction; no assertion necessary + } + + NotNull(const NotNull& rhs) = default; + NotNull& operator=(const NotNull& rhs) = default; + + /// @brief Get the stored pointer. + constexpr T get() const { return ptr_; } + + /// @brief Convert to T (the pointer type). + constexpr operator T() const { return get(); } + + /// @brief Arrow-dereference to *T (the actual value pointed to). + constexpr decltype(auto) operator->() const { return get(); } + + /// @brief Dereference to *T (the actual value pointed to). + constexpr decltype(auto) operator*() const { return *get(); } + + // It is not allowed to construct NotNull<T> with nullptr regardless of T. + NotNull(std::nullptr_t) = delete; + NotNull& operator=(std::nullptr_t) = delete; +}; + +template <class T> +NotNull(T) -> NotNull<T>; + +/// @brief Utility struct for combining several Callables (e.g. lambdas) into a single Callable with the call operator +/// overloaded. Use it to build a visitor for calling std::visit on variants. +/// @tparam ...Ts callable types +template <typename... Ts> +struct Overload : Ts... { + using Ts::operator()...; +}; + +template <typename... Ts> +Overload(Ts...) -> Overload<Ts...>; + +inline void hash_combine(std::size_t& seed) +{ + (void)seed; +} + +template <class T, typename... Rest> +inline void hash_combine(std::size_t& seed, const T& v, Rest... rest) +{ + std::hash<T> hasher; + seed ^= hasher(v) + 0x9e3779b9 + (seed << 6) + (seed >> 2); + hash_combine(seed, std::forward<Rest>(rest)...); +} + +} // namespace srb2 + +#endif // __SRB2_CXXUTIL_HPP__ diff --git a/src/d_main.c b/src/d_main.c index c139650d1eb039a057da10d063f392f33c94fac2..082df31cd5841292ad67edea07fe88bceeee051b 100644 --- a/src/d_main.c +++ b/src/d_main.c @@ -2,7 +2,7 @@ //----------------------------------------------------------------------------- // Copyright (C) 1993-1996 by id Software, Inc. // Copyright (C) 1998-2000 by DooM Legacy Team. -// Copyright (C) 1999-2023 by Sonic Team Junior. +// Copyright (C) 1999-2024 by Sonic Team Junior. // // This program is free software distributed under the // terms of the GNU General Public License, version 2. @@ -94,6 +94,8 @@ #include "lua_script.h" +#include "acs/interface.h" + // Version numbers for netplay :upside_down_face: int VERSION; int SUBVERSION; @@ -1596,6 +1598,9 @@ void D_SRB2Main(void) CONS_Printf("ST_Init(): Init status bar.\n"); ST_Init(); + CONS_Printf("ACS_Init(): Init Action Code Script VM.\n"); + ACS_Init(); + if (M_CheckParm("-room")) { if (!M_IsNextParm()) diff --git a/src/d_main.h b/src/d_main.h index 0a29f929b178dc6ca592fe59a278564418c3825c..b4615fb3c55b144d7debf6603b1e3abf0aac1b02 100644 --- a/src/d_main.h +++ b/src/d_main.h @@ -2,7 +2,7 @@ //----------------------------------------------------------------------------- // Copyright (C) 1993-1996 by id Software, Inc. // Copyright (C) 1998-2000 by DooM Legacy Team. -// Copyright (C) 1999-2023 by Sonic Team Junior. +// Copyright (C) 1999-2024 by Sonic Team Junior. // // This program is free software distributed under the // terms of the GNU General Public License, version 2. diff --git a/src/d_player.h b/src/d_player.h index 95d2f609dab322189fcb3892d7beace6d0fd45dc..a2f783cf20e1471307842c96bfdb247391a8a53b 100644 --- a/src/d_player.h +++ b/src/d_player.h @@ -2,7 +2,7 @@ //----------------------------------------------------------------------------- // Copyright (C) 1993-1996 by id Software, Inc. // Copyright (C) 1998-2000 by DooM Legacy Team. -// Copyright (C) 1999-2023 by Sonic Team Junior. +// Copyright (C) 1999-2024 by Sonic Team Junior. // // This program is free software distributed under the // terms of the GNU General Public License, version 2. @@ -614,6 +614,7 @@ typedef struct player_s boolean spectator; boolean outofcoop; boolean removing; + boolean enteredgame; boolean muted; UINT8 bot; struct player_s *botleader; diff --git a/src/deh_lua.c b/src/deh_lua.c index 64fb52fc7423a7486c9d6736762fedd0fcde64ba..8aa5ba358870899aa5dcfc98d7740200ee766882 100644 --- a/src/deh_lua.c +++ b/src/deh_lua.c @@ -414,6 +414,26 @@ static int ScanConstants(lua_State *L, boolean mathlib, const char *word) if (mathlib) return luaL_error(L, "sector triggerer '%s' could not be found.\n", word); return 0; } + else if (fastncmp("SPAC_", word, 3)) { + p = word + 5; + for (i = 0; SPAC_LIST[i]; i++) + if (fastcmp(p, SPAC_LIST[i])) { + lua_pushinteger(L, i); + return 1; + } + if (mathlib) return luaL_error(L, "line activation '%s' could not be found.\n", word); + return 0; + } + else if (fastncmp("SECSPAC_", word, 3)) { + p = word + 8; + for (i = 0; SECSPAC_LIST[i]; i++) + if (fastcmp(p, SECSPAC_LIST[i])) { + lua_pushinteger(L, i); + return 1; + } + if (mathlib) return luaL_error(L, "sector activation '%s' could not be found.\n", word); + return 0; + } else if (fastncmp("S_",word,2)) { p = word+2; for (i = 0; i < NUMSTATEFREESLOTS; i++) { diff --git a/src/deh_tables.c b/src/deh_tables.c index c7c7c604068cd4761ea3944a92cd4efecd9a0ddb..f807e574c7e415cd7bd30e4c82cdceff5bc84f43 100644 --- a/src/deh_tables.c +++ b/src/deh_tables.c @@ -4607,6 +4607,35 @@ const char *const TO_LIST[] = { NULL }; +// Line activation flags +const char *const SPAC_LIST[] = { + "REPEATSPECIAL", + "CROSS", + "CROSSMONSTER", + "CROSSMISSILE", + "PUSH", + "PUSHMONSTER", + "IMPACT", + NULL +}; + +// Sector activation flags +const char *const SECSPAC_LIST[] = { + "ONCESPECIAL", + "REPEATSPECIAL", + "CONTINUOUSSPECIAL", + "ENTER", + "FLOOR", + "CEILING", + "ENTERMONSTER", + "FLOORMONSTER", + "CEILINGMONSTER", + "ENTERMISSILE", + "FLOORMISSILE", + "CEILINGMISSILE", + NULL +}; + const char *COLOR_ENUMS[] = { "NONE", // SKINCOLOR_NONE, diff --git a/src/deh_tables.h b/src/deh_tables.h index b6986adff0166c3132be5f70ca78c7835030998f..6fd89a6950611b6a3b56596ba57ae099e127947f 100644 --- a/src/deh_tables.h +++ b/src/deh_tables.h @@ -70,6 +70,8 @@ extern const char *const MSF_LIST[]; // Sector flags extern const char *const SSF_LIST[]; // Sector special flags extern const char *const SD_LIST[]; // Sector damagetype extern const char *const TO_LIST[]; // Sector triggerer +extern const char *const SPAC_LIST[]; // Line activation flags +extern const char *const SECSPAC_LIST[]; // Sector activation flags extern const char *COLOR_ENUMS[]; extern const char *const POWERS_LIST[]; extern const char *const HUDITEMS_LIST[]; diff --git a/src/doomdata.h b/src/doomdata.h index 276e03297b6f0d453ad0d0c65fc8bd9196d9680c..785242671badab31b99ba7e65e46a3eb8b2a45d5 100644 --- a/src/doomdata.h +++ b/src/doomdata.h @@ -2,7 +2,7 @@ //----------------------------------------------------------------------------- // Copyright (C) 1993-1996 by id Software, Inc. // Copyright (C) 1998-2000 by DooM Legacy Team. -// Copyright (C) 1999-2023 by Sonic Team Junior. +// Copyright (C) 1999-2024 by Sonic Team Junior. // // This program is free software distributed under the // terms of the GNU General Public License, version 2. @@ -26,6 +26,11 @@ #include "taglist.h" #include "m_fixed.h" // See the mapthing_t scale. +// Number of args for ACS scripts. +// Increasing this requires you to also update the ACS compiler. +#define NUM_SCRIPT_ARGS 10 +#define NUM_SCRIPT_STRINGARGS 2 + // // Map level types. // The following data structures define the persistent format @@ -146,6 +151,30 @@ typedef struct #define ML_TFERLINE 32768 +enum +{ + // Special action is repeatable. + SPAC_REPEATSPECIAL = 0x00000001, + + // Activates when crossed by a player. + SPAC_CROSS = 0x00000002, + + // Activates when crossed by an enemy. + SPAC_CROSSMONSTER = 0x00000004, + + // Activates when crossed by a projectile. + SPAC_CROSSMISSILE = 0x00000008, + + // Activates when bumped by a player. + SPAC_PUSH = 0x00000010, + + // Activates when bumped by an enemy. + SPAC_PUSHMONSTER = 0x00000020, + + // Activates when bumped by a missile. + SPAC_IMPACT = 0x00000040, +}; + // Sector definition, from editing. typedef struct { @@ -200,8 +229,10 @@ typedef struct #pragma pack() #endif -#define NUMMAPTHINGARGS 10 -#define NUMMAPTHINGSTRINGARGS 2 +// Number of args for thing behaviors. +// These are safe to increase at any time. +#define NUM_MAPTHING_ARGS 10 +#define NUM_MAPTHING_STRINGARGS 2 // Thing definition, position, orientation and type, // plus visibility flags and attributes. @@ -213,11 +244,15 @@ typedef struct UINT16 options; INT16 z; UINT8 extrainfo; + mtag_t tid; taglist_t tags; fixed_t scale; fixed_t spritexscale, spriteyscale; - INT32 args[NUMMAPTHINGARGS]; - char *stringargs[NUMMAPTHINGSTRINGARGS]; + INT32 args[NUM_MAPTHING_ARGS]; + char *stringargs[NUM_MAPTHING_STRINGARGS]; + INT16 special; + INT32 script_args[NUM_SCRIPT_ARGS]; + char *script_stringargs[NUM_SCRIPT_STRINGARGS]; struct mobj_s *mobj; } mapthing_t; diff --git a/src/doomdef.h b/src/doomdef.h index 1b0e76314b13d080fc18c8f3463c066616c4b8c8..7fdd46fbe53ff79a0af4064d89d52edb5726a29e 100644 --- a/src/doomdef.h +++ b/src/doomdef.h @@ -2,7 +2,7 @@ //----------------------------------------------------------------------------- // Copyright (C) 1993-1996 by id Software, Inc. // Copyright (C) 1998-2000 by DooM Legacy Team. -// Copyright (C) 1999-2023 by Sonic Team Junior. +// Copyright (C) 1999-2024 by Sonic Team Junior. // // This program is free software distributed under the // terms of the GNU General Public License, version 2. @@ -106,6 +106,10 @@ FILE *fopenfile(const char*, const char*); +#ifdef __cplusplus +extern "C" { +#endif + //#define NOMD5 // If you don't disable ALL debug first, you get ALL debug enabled @@ -151,10 +155,10 @@ extern char logfilename[1024]; // VERSIONSTRING_RC is for the resource-definition script used by windows builds #else #ifdef BETAVERSION -#define VERSIONSTRING "v"SRB2VERSION" "BETAVERSION +#define VERSIONSTRING "v" SRB2VERSION " " BETAVERSION #define VERSIONSTRING_RC SRB2VERSION " " BETAVERSION "\0" #else -#define VERSIONSTRING "v"SRB2VERSION +#define VERSIONSTRING "v" SRB2VERSION #define VERSIONSTRING_RC SRB2VERSION "\0" #endif // Hey! If you change this, add 1 to the MODVERSION below! @@ -236,6 +240,8 @@ extern char logfilename[1024]; #define GETEXECVERSION(major,minor) (major + (minor << 16)) #define EXECVERSION GETEXECVERSION(MAJOREXECVERSION, MINOREXECVERSION) +#define UDMF_CURRENT_VERSION 0 + // ========================================================================= // The maximum number of players, multiplayer/networking. @@ -620,12 +626,14 @@ UINT32 quickncasehash (const char *p, size_t n) return x; } +#ifndef __cplusplus #ifndef min // Double-Check with WATTCP-32's cdefs.h #define min(x, y) (((x) < (y)) ? (x) : (y)) #endif #ifndef max // Double-Check with WATTCP-32's cdefs.h #define max(x, y) (((x) > (y)) ? (x) : (y)) #endif +#endif // Max gamepad/joysticks that can be detected/used. #define MAX_JOYSTICKS 4 @@ -737,4 +745,8 @@ extern int #undef UPDATE_ALERT #endif +#ifdef __cplusplus +} // extern "C" +#endif + #endif // __DOOMDEF__ diff --git a/src/fastcmp.h b/src/fastcmp.h index 0f7c24aaaa20ab3e05397c36e998f71415f43b9c..3500701f70aafbdbc83ed8093cda7907ebccc920 100644 --- a/src/fastcmp.h +++ b/src/fastcmp.h @@ -24,4 +24,11 @@ FUNCINLINE static ATTRINLINE boolean fastncmp(const char *s, const char *c, UINT return !l; // make sure you reached the end } +// case-insensitive of the above +FUNCINLINE static ATTRINLINE boolean fastnicmp(const char *s, const char *c, UINT16 l) +{ + for (; *s && toupper(*s) == toupper(*c) && --l; s++, c++) ; + return !l; // make sure you reached the end +} + #endif diff --git a/src/g_game.c b/src/g_game.c index 8d19c9e7cb68df142e68e7d0277b2425153f9206..7d1a47c22865e3173d58c839c45c14da7e848afa 100644 --- a/src/g_game.c +++ b/src/g_game.c @@ -2,7 +2,7 @@ //----------------------------------------------------------------------------- // Copyright (C) 1993-1996 by id Software, Inc. // Copyright (C) 1998-2000 by DooM Legacy Team. -// Copyright (C) 1999-2023 by Sonic Team Junior. +// Copyright (C) 1999-2024 by Sonic Team Junior. // // This program is free software distributed under the // terms of the GNU General Public License, version 2. @@ -53,6 +53,8 @@ #include "lua_hud.h" #include "lua_libs.h" +#include "acs/interface.h" + gameaction_t gameaction; gamestate_t gamestate = GS_NULL; UINT8 ultimatemode = false; @@ -2614,6 +2616,7 @@ void G_PlayerReborn(INT32 player, boolean betweenmaps) boolean spectator; boolean outofcoop; boolean removing; + boolean enteredgame; INT16 bot; SINT8 pity; INT16 rings; @@ -2630,6 +2633,7 @@ void G_PlayerReborn(INT32 player, boolean betweenmaps) quittime = players[player].quittime; spectator = players[player].spectator; outofcoop = players[player].outofcoop; + enteredgame = players[player].enteredgame; removing = players[player].removing; pflags = (players[player].pflags & (PF_FLIPCAM|PF_ANALOGMODE|PF_DIRECTIONCHAR|PF_AUTOBRAKE|PF_TAGIT|PF_GAMETYPEOVER)); playerangleturn = players[player].angleturn; @@ -2708,6 +2712,7 @@ void G_PlayerReborn(INT32 player, boolean betweenmaps) p->spectator = spectator; p->outofcoop = outofcoop; p->removing = removing; + p->enteredgame = enteredgame; p->angleturn = playerangleturn; p->oldrelangleturn = oldrelangleturn; @@ -2801,6 +2806,11 @@ void G_PlayerReborn(INT32 player, boolean betweenmaps) if (p->mare == 255) p->mare = 0; + + if (!p->spectator && p->enteredgame) + { + ACS_RunPlayerRespawnScript(p); + } } // @@ -4280,6 +4290,8 @@ static void G_DoStartContinue(void) G_PlayerFinishLevel(consoleplayer); // take away cards and stuff + ACS_RunGameOverScript(); + F_StartContinue(); gameaction = ga_nothing; } diff --git a/src/g_game.h b/src/g_game.h index 0d5fc7e373179f9b9905ab9361bb2dafb146afb7..fce963821ab89b7ac296aa21e1ccc0bf01205f83 100644 --- a/src/g_game.h +++ b/src/g_game.h @@ -2,7 +2,7 @@ //----------------------------------------------------------------------------- // Copyright (C) 1993-1996 by id Software, Inc. // Copyright (C) 1998-2000 by DooM Legacy Team. -// Copyright (C) 1999-2023 by Sonic Team Junior. +// Copyright (C) 1999-2024 by Sonic Team Junior. // // This program is free software distributed under the // terms of the GNU General Public License, version 2. @@ -21,6 +21,10 @@ #include "m_cheat.h" // objectplacing #include "m_cond.h" +#ifdef __cplusplus +extern "C" { +#endif + extern char gamedatafilename[64]; extern char timeattackfolder[64]; extern char customversionstring[32]; @@ -274,4 +278,8 @@ FUNCMATH INT32 G_TicsToMilliseconds(tic_t tics); // Don't split up TOL handling UINT32 G_TOLFlag(INT32 pgametype); +#ifdef __cplusplus +} // extern "C" +#endif + #endif diff --git a/src/hu_stuff.h b/src/hu_stuff.h index ca77ed93002750d6cefa28584d8e4de7be3bfc65..c535fecef82339c4c54c607021fb7da160322637 100644 --- a/src/hu_stuff.h +++ b/src/hu_stuff.h @@ -2,7 +2,7 @@ //----------------------------------------------------------------------------- // Copyright (C) 1993-1996 by id Software, Inc. // Copyright (C) 1998-2000 by DooM Legacy Team. -// Copyright (C) 1999-2023 by Sonic Team Junior. +// Copyright (C) 1999-2024 by Sonic Team Junior. // // This program is free software distributed under the // terms of the GNU General Public License, version 2. @@ -18,6 +18,10 @@ #include "w_wad.h" #include "r_defs.h" +#ifdef __cplusplus +extern "C" { +#endif + //------------------------------------ // Fonts & stuff //------------------------------------ @@ -123,4 +127,9 @@ void HU_DoCEcho(const char *msg); extern UINT32 hu_demoscore; extern UINT32 hu_demotime; extern UINT16 hu_demorings; + +#ifdef __cplusplus +} // extern "C" +#endif + #endif diff --git a/src/info.h b/src/info.h index 0361f64281150bec03676bd1b8a4baa36a18de22..e6a3b26bbe64ad542c315784b29c7bacb88142bf 100644 --- a/src/info.h +++ b/src/info.h @@ -20,9 +20,15 @@ #include "m_fixed.h" #include "dehacked.h" // MAX_ACTION_RECURSION +#ifdef __cplusplus +extern "C" { +#endif + // deh_tables.c now has lists for the more named enums! PLEASE keep them up to date! // For great modding!! +struct mobj_s; + // IMPORTANT! // DO NOT FORGET TO SYNC THIS LIST WITH THE ACTIONPOINTERS ARRAY IN DEH_TABLES.C enum actionnum @@ -300,274 +306,274 @@ enum actionnum // IMPORTANT NOTE: If you add/remove from this list of action // functions, don't forget to update them in deh_tables.c! -void A_Explode(); -void A_Pain(); -void A_Fall(); -void A_MonitorPop(); -void A_GoldMonitorPop(); -void A_GoldMonitorRestore(); -void A_GoldMonitorSparkle(); -void A_Look(); -void A_Chase(); -void A_FaceStabChase(); -void A_FaceStabRev(); -void A_FaceStabHurl(); -void A_FaceStabMiss(); -void A_StatueBurst(); -void A_FaceTarget(); -void A_FaceTracer(); -void A_Scream(); -void A_BossDeath(); -void A_SetShadowScale(); -void A_ShadowScream(); // MARIA!!!!!! -void A_CustomPower(); // Use this for a custom power -void A_GiveWeapon(); // Gives the player weapon(s) -void A_RingBox(); // Obtained Ring Box Tails -void A_Invincibility(); // Obtained Invincibility Box -void A_SuperSneakers(); // Obtained Super Sneakers Box -void A_BunnyHop(); // have bunny hop tails -void A_BubbleSpawn(); // Randomly spawn bubbles -void A_FanBubbleSpawn(); -void A_BubbleRise(); // Bubbles float to surface -void A_BubbleCheck(); // Don't draw if not underwater -void A_AwardScore(); -void A_ExtraLife(); // Extra Life -void A_GiveShield(); // Obtained Shield -void A_GravityBox(); -void A_ScoreRise(); // Rise the score logo -void A_AttractChase(); // Ring Chase -void A_DropMine(); // Drop Mine from Skim or Jetty-Syn Bomber -void A_FishJump(); // Fish Jump -void A_ThrownRing(); // Sparkle trail for red ring -void A_SetSolidSteam(); -void A_UnsetSolidSteam(); -void A_SignSpin(); -void A_SignPlayer(); -void A_OverlayThink(); -void A_JetChase(); -void A_JetbThink(); // Jetty-Syn Bomber Thinker -void A_JetgThink(); // Jetty-Syn Gunner Thinker -void A_JetgShoot(); // Jetty-Syn Shoot Function -void A_ShootBullet(); // JetgShoot without reactiontime setting -void A_MinusDigging(); -void A_MinusPopup(); -void A_MinusCheck(); -void A_ChickenCheck(); -void A_MouseThink(); // Mouse Thinker -void A_DetonChase(); // Deton Chaser -void A_CapeChase(); // Fake little Super Sonic cape -void A_RotateSpikeBall(); // Spike ball rotation -void A_SlingAppear(); -void A_UnidusBall(); -void A_RockSpawn(); -void A_SetFuse(); -void A_CrawlaCommanderThink(); // Crawla Commander -void A_SmokeTrailer(); -void A_RingExplode(); -void A_OldRingExplode(); -void A_MixUp(); -void A_RecyclePowers(); -void A_BossScream(); -void A_Boss2TakeDamage(); -void A_GoopSplat(); -void A_Boss2PogoSFX(); -void A_Boss2PogoTarget(); -void A_EggmanBox(); -void A_TurretFire(); -void A_SuperTurretFire(); -void A_TurretStop(); -void A_JetJawRoam(); -void A_JetJawChomp(); -void A_PointyThink(); -void A_CheckBuddy(); -void A_HoodFire(); -void A_HoodThink(); -void A_HoodFall(); -void A_ArrowBonks(); -void A_SnailerThink(); -void A_SharpChase(); -void A_SharpSpin(); -void A_SharpDecel(); -void A_CrushstaceanWalk(); -void A_CrushstaceanPunch(); -void A_CrushclawAim(); -void A_CrushclawLaunch(); -void A_VultureVtol(); -void A_VultureCheck(); -void A_VultureHover(); -void A_VultureBlast(); -void A_VultureFly(); -void A_SkimChase(); -void A_SkullAttack(); -void A_LobShot(); -void A_FireShot(); -void A_SuperFireShot(); -void A_BossFireShot(); -void A_Boss7FireMissiles(); -void A_Boss1Laser(); -void A_FocusTarget(); -void A_Boss4Reverse(); -void A_Boss4SpeedUp(); -void A_Boss4Raise(); -void A_SparkFollow(); -void A_BuzzFly(); -void A_GuardChase(); -void A_EggShield(); -void A_SetReactionTime(); -void A_Boss1Spikeballs(); -void A_Boss3TakeDamage(); -void A_Boss3Path(); -void A_Boss3ShockThink(); -void A_Shockwave(); -void A_LinedefExecute(); -void A_LinedefExecuteFromArg(); -void A_PlaySeeSound(); -void A_PlayAttackSound(); -void A_PlayActiveSound(); -void A_1upThinker(); -void A_BossZoom(); //Unused -void A_Boss1Chase(); -void A_Boss2Chase(); -void A_Boss2Pogo(); -void A_Boss7Chase(); -void A_BossJetFume(); -void A_SpawnObjectAbsolute(); -void A_SpawnObjectRelative(); -void A_ChangeAngleRelative(); -void A_ChangeAngleAbsolute(); -void A_RollAngle(); -void A_ChangeRollAngleRelative(); -void A_ChangeRollAngleAbsolute(); -void A_PlaySound(); -void A_FindTarget(); -void A_FindTracer(); -void A_SetTics(); -void A_SetRandomTics(); -void A_ChangeColorRelative(); -void A_ChangeColorAbsolute(); -void A_Dye(); -void A_SetTranslation(); -void A_MoveRelative(); -void A_MoveAbsolute(); -void A_Thrust(); -void A_ZThrust(); -void A_SetTargetsTarget(); -void A_SetObjectFlags(); -void A_SetObjectFlags2(); -void A_RandomState(); -void A_RandomStateRange(); -void A_StateRangeByAngle(); -void A_StateRangeByParameter(); -void A_DualAction(); -void A_RemoteAction(); -void A_ToggleFlameJet(); -void A_OrbitNights(); -void A_GhostMe(); -void A_SetObjectState(); -void A_SetObjectTypeState(); -void A_KnockBack(); -void A_PushAway(); -void A_RingDrain(); -void A_SplitShot(); -void A_MissileSplit(); -void A_MultiShot(); -void A_InstaLoop(); -void A_Custom3DRotate(); -void A_SearchForPlayers(); -void A_CheckRandom(); -void A_CheckTargetRings(); -void A_CheckRings(); -void A_CheckTotalRings(); -void A_CheckHealth(); -void A_CheckRange(); -void A_CheckHeight(); -void A_CheckTrueRange(); -void A_CheckThingCount(); -void A_CheckAmbush(); -void A_CheckCustomValue(); -void A_CheckCusValMemo(); -void A_SetCustomValue(); -void A_UseCusValMemo(); -void A_RelayCustomValue(); -void A_CusValAction(); -void A_ForceStop(); -void A_ForceWin(); -void A_SpikeRetract(); -void A_InfoState(); -void A_Repeat(); -void A_SetScale(); -void A_RemoteDamage(); -void A_HomingChase(); -void A_TrapShot(); -void A_VileTarget(); -void A_VileAttack(); -void A_VileFire(); -void A_BrakChase(); -void A_BrakFireShot(); -void A_BrakLobShot(); -void A_NapalmScatter(); -void A_SpawnFreshCopy(); -void A_FlickySpawn(); -void A_FlickyCenter(); -void A_FlickyAim(); -void A_FlickyFly(); -void A_FlickySoar(); -void A_FlickyCoast(); -void A_FlickyHop(); -void A_FlickyFlounder(); -void A_FlickyCheck(); -void A_FlickyHeightCheck(); -void A_FlickyFlutter(); -void A_FlameParticle(); -void A_FadeOverlay(); -void A_Boss5Jump(); -void A_LightBeamReset(); -void A_MineExplode(); -void A_MineRange(); -void A_ConnectToGround(); -void A_SpawnParticleRelative(); -void A_MultiShotDist(); -void A_WhoCaresIfYourSonIsABee(); -void A_ParentTriesToSleep(); -void A_CryingToMomma(); -void A_CheckFlags2(); -void A_Boss5FindWaypoint(); -void A_DoNPCSkid(); -void A_DoNPCPain(); -void A_PrepareRepeat(); -void A_Boss5ExtraRepeat(); -void A_Boss5Calm(); -void A_Boss5CheckOnGround(); -void A_Boss5CheckFalling(); -void A_Boss5PinchShot(); -void A_Boss5MakeItRain(); -void A_Boss5MakeJunk(); -void A_LookForBetter(); -void A_Boss5BombExplode(); -void A_DustDevilThink(); -void A_TNTExplode(); -void A_DebrisRandom(); -void A_TrainCameo(); -void A_TrainCameo2(); -void A_CanarivoreGas(); -void A_KillSegments(); -void A_SnapperSpawn(); -void A_SnapperThinker(); -void A_SaloonDoorSpawn(); -void A_MinecartSparkThink(); -void A_ModuloToState(); -void A_LavafallRocks(); -void A_LavafallLava(); -void A_FallingLavaCheck(); -void A_FireShrink(); -void A_SpawnPterabytes(); -void A_PterabyteHover(); -void A_RolloutSpawn(); -void A_RolloutRock(); -void A_DragonbomberSpawn(); -void A_DragonWing(); -void A_DragonSegment(); -void A_ChangeHeight(); +void A_Explode(struct mobj_s *actor); +void A_Pain(struct mobj_s *actor); +void A_Fall(struct mobj_s *actor); +void A_MonitorPop(struct mobj_s *actor); +void A_GoldMonitorPop(struct mobj_s *actor); +void A_GoldMonitorRestore(struct mobj_s *actor); +void A_GoldMonitorSparkle(struct mobj_s *actor); +void A_Look(struct mobj_s *actor); +void A_Chase(struct mobj_s *actor); +void A_FaceStabChase(struct mobj_s *actor); +void A_FaceStabRev(struct mobj_s *actor); +void A_FaceStabHurl(struct mobj_s *actor); +void A_FaceStabMiss(struct mobj_s *actor); +void A_StatueBurst(struct mobj_s *actor); +void A_FaceTarget(struct mobj_s *actor); +void A_FaceTracer(struct mobj_s *actor); +void A_Scream(struct mobj_s *actor); +void A_BossDeath(struct mobj_s *actor); +void A_SetShadowScale(struct mobj_s *actor); +void A_ShadowScream(struct mobj_s *actor); // MARIA!!!!!! +void A_CustomPower(struct mobj_s *actor); // Use this for a custom power +void A_GiveWeapon(struct mobj_s *actor); // Gives the player weapon(s) +void A_RingBox(struct mobj_s *actor); // Obtained Ring Box Tails +void A_Invincibility(struct mobj_s *actor); // Obtained Invincibility Box +void A_SuperSneakers(struct mobj_s *actor); // Obtained Super Sneakers Box +void A_BunnyHop(struct mobj_s *actor); // have bunny hop tails +void A_BubbleSpawn(struct mobj_s *actor); // Randomly spawn bubbles +void A_FanBubbleSpawn(struct mobj_s *actor); +void A_BubbleRise(struct mobj_s *actor); // Bubbles float to surface +void A_BubbleCheck(struct mobj_s *actor); // Don't draw if not underwater +void A_AwardScore(struct mobj_s *actor); +void A_ExtraLife(struct mobj_s *actor); // Extra Life +void A_GiveShield(struct mobj_s *actor); // Obtained Shield +void A_GravityBox(struct mobj_s *actor); +void A_ScoreRise(struct mobj_s *actor); // Rise the score logo +void A_AttractChase(struct mobj_s *actor); // Ring Chase +void A_DropMine(struct mobj_s *actor); // Drop Mine from Skim or Jetty-Syn Bomber +void A_FishJump(struct mobj_s *actor); // Fish Jump +void A_ThrownRing(struct mobj_s *actor); // Sparkle trail for red ring +void A_SetSolidSteam(struct mobj_s *actor); +void A_UnsetSolidSteam(struct mobj_s *actor); +void A_SignSpin(struct mobj_s *actor); +void A_SignPlayer(struct mobj_s *actor); +void A_OverlayThink(struct mobj_s *actor); +void A_JetChase(struct mobj_s *actor); +void A_JetbThink(struct mobj_s *actor); // Jetty-Syn Bomber Thinker +void A_JetgThink(struct mobj_s *actor); // Jetty-Syn Gunner Thinker +void A_JetgShoot(struct mobj_s *actor); // Jetty-Syn Shoot Function +void A_ShootBullet(struct mobj_s *actor); // JetgShoot without reactiontime setting +void A_MinusDigging(struct mobj_s *actor); +void A_MinusPopup(struct mobj_s *actor); +void A_MinusCheck(struct mobj_s *actor); +void A_ChickenCheck(struct mobj_s *actor); +void A_MouseThink(struct mobj_s *actor); // Mouse Thinker +void A_DetonChase(struct mobj_s *actor); // Deton Chaser +void A_CapeChase(struct mobj_s *actor); // Fake little Super Sonic cape +void A_RotateSpikeBall(struct mobj_s *actor); // Spike ball rotation +void A_SlingAppear(struct mobj_s *actor); +void A_UnidusBall(struct mobj_s *actor); +void A_RockSpawn(struct mobj_s *actor); +void A_SetFuse(struct mobj_s *actor); +void A_CrawlaCommanderThink(struct mobj_s *actor); // Crawla Commander +void A_SmokeTrailer(struct mobj_s *actor); +void A_RingExplode(struct mobj_s *actor); +void A_OldRingExplode(struct mobj_s *actor); +void A_MixUp(struct mobj_s *actor); +void A_RecyclePowers(struct mobj_s *actor); +void A_BossScream(struct mobj_s *actor); +void A_Boss2TakeDamage(struct mobj_s *actor); +void A_GoopSplat(struct mobj_s *actor); +void A_Boss2PogoSFX(struct mobj_s *actor); +void A_Boss2PogoTarget(struct mobj_s *actor); +void A_EggmanBox(struct mobj_s *actor); +void A_TurretFire(struct mobj_s *actor); +void A_SuperTurretFire(struct mobj_s *actor); +void A_TurretStop(struct mobj_s *actor); +void A_JetJawRoam(struct mobj_s *actor); +void A_JetJawChomp(struct mobj_s *actor); +void A_PointyThink(struct mobj_s *actor); +void A_CheckBuddy(struct mobj_s *actor); +void A_HoodFire(struct mobj_s *actor); +void A_HoodThink(struct mobj_s *actor); +void A_HoodFall(struct mobj_s *actor); +void A_ArrowBonks(struct mobj_s *actor); +void A_SnailerThink(struct mobj_s *actor); +void A_SharpChase(struct mobj_s *actor); +void A_SharpSpin(struct mobj_s *actor); +void A_SharpDecel(struct mobj_s *actor); +void A_CrushstaceanWalk(struct mobj_s *actor); +void A_CrushstaceanPunch(struct mobj_s *actor); +void A_CrushclawAim(struct mobj_s *actor); +void A_CrushclawLaunch(struct mobj_s *actor); +void A_VultureVtol(struct mobj_s *actor); +void A_VultureCheck(struct mobj_s *actor); +void A_VultureHover(struct mobj_s *actor); +void A_VultureBlast(struct mobj_s *actor); +void A_VultureFly(struct mobj_s *actor); +void A_SkimChase(struct mobj_s *actor); +void A_SkullAttack(struct mobj_s *actor); +void A_LobShot(struct mobj_s *actor); +void A_FireShot(struct mobj_s *actor); +void A_SuperFireShot(struct mobj_s *actor); +void A_BossFireShot(struct mobj_s *actor); +void A_Boss7FireMissiles(struct mobj_s *actor); +void A_Boss1Laser(struct mobj_s *actor); +void A_FocusTarget(struct mobj_s *actor); +void A_Boss4Reverse(struct mobj_s *actor); +void A_Boss4SpeedUp(struct mobj_s *actor); +void A_Boss4Raise(struct mobj_s *actor); +void A_SparkFollow(struct mobj_s *actor); +void A_BuzzFly(struct mobj_s *actor); +void A_GuardChase(struct mobj_s *actor); +void A_EggShield(struct mobj_s *actor); +void A_SetReactionTime(struct mobj_s *actor); +void A_Boss1Spikeballs(struct mobj_s *actor); +void A_Boss3TakeDamage(struct mobj_s *actor); +void A_Boss3Path(struct mobj_s *actor); +void A_Boss3ShockThink(struct mobj_s *actor); +void A_Shockwave(struct mobj_s *actor); +void A_LinedefExecute(struct mobj_s *actor); +void A_LinedefExecuteFromArg(struct mobj_s *actor); +void A_PlaySeeSound(struct mobj_s *actor); +void A_PlayAttackSound(struct mobj_s *actor); +void A_PlayActiveSound(struct mobj_s *actor); +void A_1upThinker(struct mobj_s *actor); +void A_BossZoom(struct mobj_s *actor); //Unused +void A_Boss1Chase(struct mobj_s *actor); +void A_Boss2Chase(struct mobj_s *actor); +void A_Boss2Pogo(struct mobj_s *actor); +void A_Boss7Chase(struct mobj_s *actor); +void A_BossJetFume(struct mobj_s *actor); +void A_SpawnObjectAbsolute(struct mobj_s *actor); +void A_SpawnObjectRelative(struct mobj_s *actor); +void A_ChangeAngleRelative(struct mobj_s *actor); +void A_ChangeAngleAbsolute(struct mobj_s *actor); +void A_RollAngle(struct mobj_s *actor); +void A_ChangeRollAngleRelative(struct mobj_s *actor); +void A_ChangeRollAngleAbsolute(struct mobj_s *actor); +void A_PlaySound(struct mobj_s *actor); +void A_FindTarget(struct mobj_s *actor); +void A_FindTracer(struct mobj_s *actor); +void A_SetTics(struct mobj_s *actor); +void A_SetRandomTics(struct mobj_s *actor); +void A_ChangeColorRelative(struct mobj_s *actor); +void A_ChangeColorAbsolute(struct mobj_s *actor); +void A_Dye(struct mobj_s *actor); +void A_SetTranslation(struct mobj_s *actor); +void A_MoveRelative(struct mobj_s *actor); +void A_MoveAbsolute(struct mobj_s *actor); +void A_Thrust(struct mobj_s *actor); +void A_ZThrust(struct mobj_s *actor); +void A_SetTargetsTarget(struct mobj_s *actor); +void A_SetObjectFlags(struct mobj_s *actor); +void A_SetObjectFlags2(struct mobj_s *actor); +void A_RandomState(struct mobj_s *actor); +void A_RandomStateRange(struct mobj_s *actor); +void A_StateRangeByAngle(struct mobj_s *actor); +void A_StateRangeByParameter(struct mobj_s *actor); +void A_DualAction(struct mobj_s *actor); +void A_RemoteAction(struct mobj_s *actor); +void A_ToggleFlameJet(struct mobj_s *actor); +void A_OrbitNights(struct mobj_s *actor); +void A_GhostMe(struct mobj_s *actor); +void A_SetObjectState(struct mobj_s *actor); +void A_SetObjectTypeState(struct mobj_s *actor); +void A_KnockBack(struct mobj_s *actor); +void A_PushAway(struct mobj_s *actor); +void A_RingDrain(struct mobj_s *actor); +void A_SplitShot(struct mobj_s *actor); +void A_MissileSplit(struct mobj_s *actor); +void A_MultiShot(struct mobj_s *actor); +void A_InstaLoop(struct mobj_s *actor); +void A_Custom3DRotate(struct mobj_s *actor); +void A_SearchForPlayers(struct mobj_s *actor); +void A_CheckRandom(struct mobj_s *actor); +void A_CheckTargetRings(struct mobj_s *actor); +void A_CheckRings(struct mobj_s *actor); +void A_CheckTotalRings(struct mobj_s *actor); +void A_CheckHealth(struct mobj_s *actor); +void A_CheckRange(struct mobj_s *actor); +void A_CheckHeight(struct mobj_s *actor); +void A_CheckTrueRange(struct mobj_s *actor); +void A_CheckThingCount(struct mobj_s *actor); +void A_CheckAmbush(struct mobj_s *actor); +void A_CheckCustomValue(struct mobj_s *actor); +void A_CheckCusValMemo(struct mobj_s *actor); +void A_SetCustomValue(struct mobj_s *actor); +void A_UseCusValMemo(struct mobj_s *actor); +void A_RelayCustomValue(struct mobj_s *actor); +void A_CusValAction(struct mobj_s *actor); +void A_ForceStop(struct mobj_s *actor); +void A_ForceWin(struct mobj_s *actor); +void A_SpikeRetract(struct mobj_s *actor); +void A_InfoState(struct mobj_s *actor); +void A_Repeat(struct mobj_s *actor); +void A_SetScale(struct mobj_s *actor); +void A_RemoteDamage(struct mobj_s *actor); +void A_HomingChase(struct mobj_s *actor); +void A_TrapShot(struct mobj_s *actor); +void A_VileTarget(struct mobj_s *actor); +void A_VileAttack(struct mobj_s *actor); +void A_VileFire(struct mobj_s *actor); +void A_BrakChase(struct mobj_s *actor); +void A_BrakFireShot(struct mobj_s *actor); +void A_BrakLobShot(struct mobj_s *actor); +void A_NapalmScatter(struct mobj_s *actor); +void A_SpawnFreshCopy(struct mobj_s *actor); +void A_FlickySpawn(struct mobj_s *actor); +void A_FlickyCenter(struct mobj_s *actor); +void A_FlickyAim(struct mobj_s *actor); +void A_FlickyFly(struct mobj_s *actor); +void A_FlickySoar(struct mobj_s *actor); +void A_FlickyCoast(struct mobj_s *actor); +void A_FlickyHop(struct mobj_s *actor); +void A_FlickyFlounder(struct mobj_s *actor); +void A_FlickyCheck(struct mobj_s *actor); +void A_FlickyHeightCheck(struct mobj_s *actor); +void A_FlickyFlutter(struct mobj_s *actor); +void A_FlameParticle(struct mobj_s *actor); +void A_FadeOverlay(struct mobj_s *actor); +void A_Boss5Jump(struct mobj_s *actor); +void A_LightBeamReset(struct mobj_s *actor); +void A_MineExplode(struct mobj_s *actor); +void A_MineRange(struct mobj_s *actor); +void A_ConnectToGround(struct mobj_s *actor); +void A_SpawnParticleRelative(struct mobj_s *actor); +void A_MultiShotDist(struct mobj_s *actor); +void A_WhoCaresIfYourSonIsABee(struct mobj_s *actor); +void A_ParentTriesToSleep(struct mobj_s *actor); +void A_CryingToMomma(struct mobj_s *actor); +void A_CheckFlags2(struct mobj_s *actor); +void A_Boss5FindWaypoint(struct mobj_s *actor); +void A_DoNPCSkid(struct mobj_s *actor); +void A_DoNPCPain(struct mobj_s *actor); +void A_PrepareRepeat(struct mobj_s *actor); +void A_Boss5ExtraRepeat(struct mobj_s *actor); +void A_Boss5Calm(struct mobj_s *actor); +void A_Boss5CheckOnGround(struct mobj_s *actor); +void A_Boss5CheckFalling(struct mobj_s *actor); +void A_Boss5PinchShot(struct mobj_s *actor); +void A_Boss5MakeItRain(struct mobj_s *actor); +void A_Boss5MakeJunk(struct mobj_s *actor); +void A_LookForBetter(struct mobj_s *actor); +void A_Boss5BombExplode(struct mobj_s *actor); +void A_DustDevilThink(struct mobj_s *actor); +void A_TNTExplode(struct mobj_s *actor); +void A_DebrisRandom(struct mobj_s *actor); +void A_TrainCameo(struct mobj_s *actor); +void A_TrainCameo2(struct mobj_s *actor); +void A_CanarivoreGas(struct mobj_s *actor); +void A_KillSegments(struct mobj_s *actor); +void A_SnapperSpawn(struct mobj_s *actor); +void A_SnapperThinker(struct mobj_s *actor); +void A_SaloonDoorSpawn(struct mobj_s *actor); +void A_MinecartSparkThink(struct mobj_s *actor); +void A_ModuloToState(struct mobj_s *actor); +void A_LavafallRocks(struct mobj_s *actor); +void A_LavafallLava(struct mobj_s *actor); +void A_FallingLavaCheck(struct mobj_s *actor); +void A_FireShrink(struct mobj_s *actor); +void A_SpawnPterabytes(struct mobj_s *actor); +void A_PterabyteHover(struct mobj_s *actor); +void A_RolloutSpawn(struct mobj_s *actor); +void A_RolloutRock(struct mobj_s *actor); +void A_DragonbomberSpawn(struct mobj_s *actor); +void A_DragonWing(struct mobj_s *actor); +void A_DragonSegment(struct mobj_s *actor); +void A_ChangeHeight(struct mobj_s *actor); extern int actionsoverridden[NUMACTIONS][MAX_ACTION_RECURSION]; @@ -5205,4 +5211,8 @@ void P_BackupTables(void); void P_ResetData(INT32 flags); +#ifdef __cplusplus +} // extern "C" +#endif + #endif diff --git a/src/lua_baselib.c b/src/lua_baselib.c index f6b8f462b5a41cee30c33145fd62fa28daa01e92..9ad504668d765beadf45b069ed8602df10bc47c9 100644 --- a/src/lua_baselib.c +++ b/src/lua_baselib.c @@ -1,7 +1,7 @@ // SONIC ROBO BLAST 2 //----------------------------------------------------------------------------- // Copyright (C) 2012-2016 by John "JTE" Muniz. -// Copyright (C) 2012-2023 by Sonic Team Junior. +// Copyright (C) 2012-2024 by Sonic Team Junior. // // This program is free software distributed under the // terms of the GNU General Public License, version 2. @@ -216,8 +216,14 @@ static const struct { {META_LINEARGS, "line_t.args"}, {META_LINESTRINGARGS, "line_t.stringargs"}, + {META_SECTORARGS, "sector_t.args"}, + {META_SECTORSTRINGARGS, "sector_t.stringargs"}, + {META_THINGARGS, "mapthing.args"}, {META_THINGSTRINGARGS, "mapthing.stringargs"}, + + {META_THINGSPECIALARGS, "mapthing.specialargs"}, + {META_THINGSPECIALSTRINGARGS, "mapthing.specialstringargs"}, #ifdef HAVE_LUA_SEGS {META_NODEBBOX, "node_t.bbox"}, {META_NODECHILDREN, "node_t.children"}, @@ -3860,80 +3866,32 @@ static int lib_gAddGametype(lua_State *L) // Partly lifted from Got_AddPlayer static int lib_gAddPlayer(lua_State *L) { - INT16 i, newplayernum; - player_t *newplayer; - SINT8 skinnum = 0, bot; - - for (i = 0; i < MAXPLAYERS; i++) - { - if (!playeringame[i]) - break; - } - - if (i >= MAXPLAYERS) - { - lua_pushnil(L); - return 1; - } - - newplayernum = i; - - CL_ClearPlayer(newplayernum); - - playeringame[newplayernum] = true; - G_AddPlayer(newplayernum); - newplayer = &players[newplayernum]; + const char *skinname = NULL; + UINT16 skincolor = SKINCOLOR_NONE; + const char *botname = NULL; + SINT8 bottype; - newplayer->jointime = 0; - newplayer->quittime = 0; - newplayer->lastinputtime = 0; - - // Read the skin argument (defaults to Sonic) + // Read the skin argument if (!lua_isnoneornil(L, 1)) - { - skinnum = R_SkinAvailable(luaL_checkstring(L, 1)); - skinnum = skinnum < 0 ? 0 : skinnum; - } + skinname = luaL_checkstring(L, 1); - // Read the color (defaults to skin prefcolor) + // Read the color if (!lua_isnoneornil(L, 2)) - newplayer->skincolor = R_GetColorByName(luaL_checkstring(L, 2)); - else - newplayer->skincolor = skins[skinnum]->prefcolor; - - // Set the bot default name as the skin - strcpy(player_names[newplayernum], skins[skinnum]->realname); + skincolor = R_GetColorByName(luaL_checkstring(L, 2)); // Read the bot name, if given if (!lua_isnoneornil(L, 3)) - strlcpy(player_names[newplayernum], luaL_checkstring(L, 3), sizeof(*player_names)); - - bot = luaL_optinteger(L, 4, 3); - newplayer->bot = (bot >= BOT_NONE && bot <= BOT_MPAI) ? bot : BOT_MPAI; - - // If our bot is a 2P type, we'll need to set its leader so it can spawn - if (newplayer->bot == BOT_2PAI || newplayer->bot == BOT_2PHUMAN) - B_UpdateBotleader(newplayer); + botname = luaL_checkstring(L, 3); - // Set the skin (can't do this until AFTER bot type is set!) - SetPlayerSkinByNum(newplayernum, skinnum); + bottype = luaL_optinteger(L, 4, 3); - if (netgame) - { - char joinmsg[256]; - - // Truncate bot name - player_names[newplayernum][sizeof(*player_names) - 8] = '\0'; // The length of colored [BOT] + 1 - - strcpy(joinmsg, M_GetText("\x82*Bot %s has joined the game (player %d)")); - strcpy(joinmsg, va(joinmsg, player_names[newplayernum], newplayernum)); - HU_AddChatText(joinmsg, false); - - // Append blue [BOT] tag at the end - strlcat(player_names[newplayernum], "\x84[BOT]\x80", sizeof(*player_names)); + INT16 playernum = B_AddBot(skinname, skincolor, botname, bottype); + if (playernum < 0) { + lua_pushnil(L); + return 1; } - LUA_PushUserdata(L, newplayer, META_PLAYER); + LUA_PushUserdata(L, &players[playernum], META_PLAYER); return 1; } diff --git a/src/lua_libs.h b/src/lua_libs.h index a90d8ac7fb800c426e7bef94538803366a835b8b..931f196dc398df23ff862901eaa31ea2939df5ef 100644 --- a/src/lua_libs.h +++ b/src/lua_libs.h @@ -1,7 +1,7 @@ // SONIC ROBO BLAST 2 //----------------------------------------------------------------------------- // Copyright (C) 2012-2016 by John "JTE" Muniz. -// Copyright (C) 2012-2023 by Sonic Team Junior. +// Copyright (C) 2012-2024 by Sonic Team Junior. // // This program is free software distributed under the // terms of the GNU General Public License, version 2. @@ -71,8 +71,12 @@ extern boolean ignoregameinputs; #define META_SIDENUM "LINE_T*SIDENUM" #define META_LINEARGS "LINE_T*ARGS" #define META_LINESTRINGARGS "LINE_T*STRINGARGS" +#define META_SECTORARGS "SECTOR_T*ARGS" +#define META_SECTORSTRINGARGS "SECTOR_T*STRINGARGS" #define META_THINGARGS "MAPTHING_T*ARGS" #define META_THINGSTRINGARGS "MAPTHING_T*STRINGARGS" +#define META_THINGSPECIALARGS "MAPTHING_T*SPECIALARGS" +#define META_THINGSPECIALSTRINGARGS "MAPTHING_T*SPECIALSTRINGARGS" #define META_POLYOBJVERTICES "POLYOBJ_T*VERTICES" #define META_POLYOBJLINES "POLYOBJ_T*LINES" #ifdef HAVE_LUA_SEGS diff --git a/src/lua_maplib.c b/src/lua_maplib.c index 6b489f22b1939941c0ebf83a5a3ae0b7e215de5a..328a8b6e5220035033d7e6d058e5dbb1dfb4802f 100644 --- a/src/lua_maplib.c +++ b/src/lua_maplib.c @@ -1,7 +1,7 @@ // SONIC ROBO BLAST 2 //----------------------------------------------------------------------------- // Copyright (C) 2012-2016 by John "JTE" Muniz. -// Copyright (C) 2012-2023 by Sonic Team Junior. +// Copyright (C) 2012-2024 by Sonic Team Junior. // // This program is free software distributed under the // terms of the GNU General Public License, version 2. @@ -69,6 +69,10 @@ enum sector_e { sector_triggerer, sector_friction, sector_gravity, + sector_action, + sector_args, + sector_stringargs, + sector_activation, }; static const char *const sector_opt[] = { @@ -112,6 +116,10 @@ static const char *const sector_opt[] = { "triggerer", "friction", "gravity", + "action", + "args", + "stringargs", + "activation", NULL}; static int sector_fields_ref = LUA_NOREF; @@ -142,6 +150,7 @@ enum line_e { line_dy, line_angle, line_flags, + line_activation, line_special, line_tag, line_taglist, @@ -168,6 +177,7 @@ static const char *const line_opt[] = { "dy", "angle", "flags", + "activation", "special", "tag", "taglist", @@ -662,6 +672,42 @@ static int sectorlines_num(lua_State *L) // sector_t // ////////////// +// args, i -> args[i] +static int sectorargs_get(lua_State *L) +{ + INT32 *args = *((INT32**)luaL_checkudata(L, 1, META_SECTORARGS)); + int i = luaL_checkinteger(L, 2); + if (i < 0 || i >= NUM_SCRIPT_ARGS) + return luaL_error(L, LUA_QL("sector_t.args") " index cannot be %d", i); + lua_pushinteger(L, args[i]); + return 1; +} + +// #args -> NUM_SCRIPT_ARGS +static int sectorargs_len(lua_State* L) +{ + lua_pushinteger(L, NUM_SCRIPT_ARGS); + return 1; +} + +// stringargs, i -> stringargs[i] +static int sectorstringargs_get(lua_State *L) +{ + char **stringargs = *((char***)luaL_checkudata(L, 1, META_SECTORSTRINGARGS)); + int i = luaL_checkinteger(L, 2); + if (i < 0 || i >= NUM_SCRIPT_STRINGARGS) + return luaL_error(L, LUA_QL("sector_t.stringargs") " index cannot be %d", i); + lua_pushstring(L, stringargs[i]); + return 1; +} + +// #stringargs -> NUM_SCRIPT_STRINGARGS +static int sectorstringargs_len(lua_State *L) +{ + lua_pushinteger(L, NUM_SCRIPT_STRINGARGS); + return 1; +} + static int sector_get(lua_State *L) { sector_t *sector = *((sector_t **)luaL_checkudata(L, 1, META_SECTOR)); @@ -819,6 +865,18 @@ static int sector_get(lua_State *L) case sector_gravity: // gravity lua_pushfixed(L, sector->gravity); return 1; + case sector_action: // action + lua_pushinteger(L, (INT16)sector->action); + return 1; + case sector_args: + LUA_PushUserdata(L, sector->args, META_SECTORARGS); + return 1; + case sector_stringargs: + LUA_PushUserdata(L, sector->stringargs, META_SECTORSTRINGARGS); + return 1; + case sector_activation: + lua_pushinteger(L, sector->activation); + return 1; } return 0; } @@ -847,6 +905,8 @@ static int sector_set(lua_State *L) case sector_fslope: // f_slope case sector_cslope: // c_slope case sector_friction: // friction + case sector_args: // args + case sector_stringargs: // stringargs return luaL_error(L, "sector_t field " LUA_QS " cannot be set.", sector_opt[field]); default: return luaL_error(L, "sector_t has no field named " LUA_QS ".", lua_tostring(L, 2)); @@ -962,6 +1022,12 @@ static int sector_set(lua_State *L) case sector_gravity: sector->gravity = luaL_checkfixed(L, 3); break; + case sector_action: + sector->action = (INT16)luaL_checkinteger(L, 3); + break; + case sector_activation: + sector->activation = luaL_checkinteger(L, 3); + break; } return 0; } @@ -1030,16 +1096,16 @@ static int lineargs_get(lua_State *L) { INT32 *args = *((INT32**)luaL_checkudata(L, 1, META_LINEARGS)); int i = luaL_checkinteger(L, 2); - if (i < 0 || i >= NUMLINEARGS) + if (i < 0 || i >= NUM_SCRIPT_ARGS) return luaL_error(L, LUA_QL("line_t.args") " index cannot be %d", i); lua_pushinteger(L, args[i]); return 1; } -// #args -> NUMLINEARGS +// #args -> NUM_SCRIPT_ARGS static int lineargs_len(lua_State* L) { - lua_pushinteger(L, NUMLINEARGS); + lua_pushinteger(L, NUM_SCRIPT_ARGS); return 1; } @@ -1048,16 +1114,16 @@ static int linestringargs_get(lua_State *L) { char **stringargs = *((char***)luaL_checkudata(L, 1, META_LINESTRINGARGS)); int i = luaL_checkinteger(L, 2); - if (i < 0 || i >= NUMLINESTRINGARGS) + if (i < 0 || i >= NUM_SCRIPT_STRINGARGS) return luaL_error(L, LUA_QL("line_t.stringargs") " index cannot be %d", i); lua_pushstring(L, stringargs[i]); return 1; } -// #stringargs -> NUMLINESTRINGARGS +// #stringargs -> NUM_SCRIPT_STRINGARGS static int linestringargs_len(lua_State *L) { - lua_pushinteger(L, NUMLINESTRINGARGS); + lua_pushinteger(L, NUM_SCRIPT_STRINGARGS); return 1; } @@ -1098,6 +1164,9 @@ static int line_get(lua_State *L) case line_flags: lua_pushinteger(L, line->flags); return 1; + case line_activation: + lua_pushinteger(L, line->activation); + return 1; case line_special: lua_pushinteger(L, line->special); return 1; @@ -3000,6 +3069,8 @@ int LUA_MapLib(lua_State *L) LUA_RegisterUserdataMetatable(L, META_LINE, line_get, NULL, line_num); LUA_RegisterUserdataMetatable(L, META_LINEARGS, lineargs_get, NULL, lineargs_len); LUA_RegisterUserdataMetatable(L, META_LINESTRINGARGS, linestringargs_get, NULL, linestringargs_len); + LUA_RegisterUserdataMetatable(L, META_SECTORARGS, sectorargs_get, NULL, sectorargs_len); + LUA_RegisterUserdataMetatable(L, META_SECTORSTRINGARGS, sectorstringargs_get, NULL, sectorstringargs_len); LUA_RegisterUserdataMetatable(L, META_SIDENUM, sidenum_get, NULL, NULL); LUA_RegisterUserdataMetatable(L, META_SIDE, side_get, side_set, side_num); LUA_RegisterUserdataMetatable(L, META_VERTEX, vertex_get, NULL, vertex_num); diff --git a/src/lua_mobjlib.c b/src/lua_mobjlib.c index de7065790abb137e82f505c961215ca6acb10429..380f279f976f7387f88a0a4933bdadeee6b43a8c 100644 --- a/src/lua_mobjlib.c +++ b/src/lua_mobjlib.c @@ -1,7 +1,7 @@ // SONIC ROBO BLAST 2 //----------------------------------------------------------------------------- // Copyright (C) 2012-2016 by John "JTE" Muniz. -// Copyright (C) 2012-2023 by Sonic Team Junior. +// Copyright (C) 2012-2024 by Sonic Team Junior. // // This program is free software distributed under the // terms of the GNU General Public License, version 2. @@ -102,7 +102,13 @@ enum mobj_e { mobj_colorized, mobj_mirrored, mobj_shadowscale, - mobj_dispoffset + mobj_dispoffset, + mobj_tid, + mobj_args, + mobj_stringargs, + mobj_special, + mobj_specialargs, + mobj_specialstringargs }; static const char *const mobj_opt[] = { @@ -184,6 +190,12 @@ static const char *const mobj_opt[] = { "mirrored", "shadowscale", "dispoffset", + "tid", + "args", + "stringargs", + "special", + "specialargs", + "specialstringargs", NULL}; #define UNIMPLEMENTED luaL_error(L, LUA_QL("mobj_t") " field " LUA_QS " is not implemented for Lua and cannot be accessed.", mobj_opt[field]) @@ -481,6 +493,24 @@ static int mobj_get(lua_State *L) case mobj_dispoffset: lua_pushinteger(L, mo->dispoffset); break; + case mobj_tid: + lua_pushinteger(L, mo->tid); + break; + case mobj_args: + LUA_PushUserdata(L, mo->thing_args, META_THINGARGS); + break; + case mobj_stringargs: + LUA_PushUserdata(L, mo->thing_stringargs, META_THINGSTRINGARGS); + break; + case mobj_special: + lua_pushinteger(L, mo->special); + break; + case mobj_specialargs: + LUA_PushUserdata(L, mo->script_args, META_THINGSPECIALARGS); + break; + case mobj_specialstringargs: + LUA_PushUserdata(L, mo->script_stringargs, META_THINGSPECIALSTRINGARGS); + break; default: // extra custom variables in Lua memory lua_getfield(L, LUA_REGISTRYINDEX, LREG_EXTVARS); I_Assert(lua_istable(L, -1)); @@ -876,6 +906,17 @@ static int mobj_set(lua_State *L) case mobj_dispoffset: mo->dispoffset = luaL_checkinteger(L, 3); break; + case mobj_tid: + P_SetThingTID(mo, luaL_checkinteger(L, 3)); + break; + case mobj_special: + mo->special = luaL_checkinteger(L, 3); + break; + case mobj_args: + case mobj_stringargs: + case mobj_specialargs: + case mobj_specialstringargs: + return NOSET; default: lua_getfield(L, LUA_REGISTRYINDEX, LREG_EXTVARS); I_Assert(lua_istable(L, -1)); @@ -909,16 +950,16 @@ static int thingargs_get(lua_State *L) { INT32 *args = *((INT32**)luaL_checkudata(L, 1, META_THINGARGS)); int i = luaL_checkinteger(L, 2); - if (i < 0 || i >= NUMMAPTHINGARGS) + if (i < 0 || i >= NUM_MAPTHING_ARGS) return luaL_error(L, LUA_QL("mapthing_t.args") " index cannot be %d", i); lua_pushinteger(L, args[i]); return 1; } -// #args -> NUMMAPTHINGARGS +// #args -> NUM_MAPTHING_ARGS static int thingargs_len(lua_State* L) { - lua_pushinteger(L, NUMMAPTHINGARGS); + lua_pushinteger(L, NUM_MAPTHING_ARGS); return 1; } @@ -927,16 +968,52 @@ static int thingstringargs_get(lua_State *L) { char **stringargs = *((char***)luaL_checkudata(L, 1, META_THINGSTRINGARGS)); int i = luaL_checkinteger(L, 2); - if (i < 0 || i >= NUMMAPTHINGSTRINGARGS) + if (i < 0 || i >= NUM_MAPTHING_STRINGARGS) return luaL_error(L, LUA_QL("mapthing_t.stringargs") " index cannot be %d", i); lua_pushstring(L, stringargs[i]); return 1; } -// #stringargs -> NUMMAPTHINGSTRINGARGS +// #stringargs -> NUM_MAPTHING_STRINGARGS static int thingstringargs_len(lua_State *L) { - lua_pushinteger(L, NUMMAPTHINGSTRINGARGS); + lua_pushinteger(L, NUM_MAPTHING_STRINGARGS); + return 1; +} + +// args, i -> args[i] +static int thingspecialargs_get(lua_State *L) +{ + INT32 *args = *((INT32**)luaL_checkudata(L, 1, META_THINGSPECIALARGS)); + int i = luaL_checkinteger(L, 2); + if (i < 0 || i >= NUM_SCRIPT_ARGS) + return luaL_error(L, LUA_QL("mapthing_t.specialargs") " index cannot be %d", i); + lua_pushinteger(L, args[i]); + return 1; +} + +// #args -> NUM_SCRIPT_ARGS +static int thingspecialargs_len(lua_State* L) +{ + lua_pushinteger(L, NUM_SCRIPT_ARGS); + return 1; +} + +// stringargs, i -> stringargs[i] +static int thingspecialstringargs_get(lua_State *L) +{ + char **stringargs = *((char***)luaL_checkudata(L, 1, META_THINGSPECIALSTRINGARGS)); + int i = luaL_checkinteger(L, 2); + if (i < 0 || i >= NUM_SCRIPT_STRINGARGS) + return luaL_error(L, LUA_QL("mapthing_t.specialstringargs") " index cannot be %d", i); + lua_pushstring(L, stringargs[i]); + return 1; +} + +// #stringargs -> NUM_SCRIPT_STRINGARGS +static int thingspecialstringargs_len(lua_State *L) +{ + lua_pushinteger(L, NUM_SCRIPT_STRINGARGS); return 1; } @@ -958,6 +1035,9 @@ enum mapthing_e { mapthing_taglist, mapthing_args, mapthing_stringargs, + mapthing_special, + mapthing_specialargs, + mapthing_specialstringargs, mapthing_mobj, }; @@ -979,6 +1059,9 @@ const char *const mapthing_opt[] = { "taglist", "args", "stringargs", + "special", + "specialargs", + "specialstringargs", "mobj", NULL, }; @@ -1057,6 +1140,15 @@ static int mapthing_get(lua_State *L) case mapthing_stringargs: LUA_PushUserdata(L, mt->stringargs, META_THINGSTRINGARGS); break; + case mapthing_special: + lua_pushinteger(L, mt->special); + break; + case mapthing_specialargs: + LUA_PushUserdata(L, mt->script_args, META_THINGSPECIALARGS); + break; + case mapthing_specialstringargs: + LUA_PushUserdata(L, mt->script_stringargs, META_THINGSPECIALSTRINGARGS); + break; case mapthing_mobj: LUA_PushUserdata(L, mt->mobj, META_MOBJ); break; @@ -1135,6 +1227,17 @@ static int mapthing_set(lua_State *L) break; case mapthing_taglist: return LUA_ErrSetDirectly(L, "mapthing_t", "taglist"); + case mapthing_special: + mt->special = (INT16)luaL_checkinteger(L, 3); + break; + case mapthing_args: + return LUA_ErrSetDirectly(L, "mapthing_t", "args"); + case mapthing_stringargs: + return LUA_ErrSetDirectly(L, "mapthing_t", "stringargs"); + case mapthing_specialargs: + return LUA_ErrSetDirectly(L, "mapthing_t", "specialargs"); + case mapthing_specialstringargs: + return LUA_ErrSetDirectly(L, "mapthing_t", "specialstringargs"); case mapthing_mobj: mt->mobj = *((mobj_t **)luaL_checkudata(L, 3, META_MOBJ)); break; @@ -1197,6 +1300,8 @@ int LUA_MobjLib(lua_State *L) LUA_RegisterUserdataMetatable(L, META_MOBJ, mobj_get, mobj_set, NULL); LUA_RegisterUserdataMetatable(L, META_THINGARGS, thingargs_get, NULL, thingargs_len); LUA_RegisterUserdataMetatable(L, META_THINGSTRINGARGS, thingstringargs_get, NULL, thingstringargs_len); + LUA_RegisterUserdataMetatable(L, META_THINGSPECIALARGS, thingspecialargs_get, NULL, thingspecialargs_len); + LUA_RegisterUserdataMetatable(L, META_THINGSPECIALSTRINGARGS, thingspecialstringargs_get, NULL, thingspecialstringargs_len); LUA_RegisterUserdataMetatable(L, META_MAPTHING, mapthing_get, mapthing_set, mapthing_num); mobj_fields_ref = Lua_CreateFieldTable(L, mobj_opt); diff --git a/src/lua_script.c b/src/lua_script.c index 057899555480383611652c552e41bf41398b0e2b..fee2cf9eea2875bbc57dd536aeaeeb5982280469 100644 --- a/src/lua_script.c +++ b/src/lua_script.c @@ -1001,6 +1001,8 @@ void LUA_InvalidateMapthings(void) LUA_InvalidateUserdata(&mapthings[i].tags); LUA_InvalidateUserdata(mapthings[i].args); LUA_InvalidateUserdata(mapthings[i].stringargs); + LUA_InvalidateUserdata(mapthings[i].script_args); + LUA_InvalidateUserdata(mapthings[i].script_stringargs); } } diff --git a/src/m_cheat.c b/src/m_cheat.c index 07f10071711025f93f91d7b7ea2d9d65f3d5ad1d..2e87e5ebca8e3fb7aee5d0f69fae0d0fc5b26b45 100644 --- a/src/m_cheat.c +++ b/src/m_cheat.c @@ -2,7 +2,7 @@ //----------------------------------------------------------------------------- // Copyright (C) 1993-1996 by id Software, Inc. // Copyright (C) 1998-2000 by DooM Legacy Team. -// Copyright (C) 1999-2023 by Sonic Team Junior. +// Copyright (C) 1999-2024 by Sonic Team Junior. // // This program is free software distributed under the // terms of the GNU General Public License, version 2. @@ -1105,9 +1105,6 @@ static mapthing_t *OP_CreateNewMapThing(player_t *player, UINT16 type, boolean c mt->scale = player->mo->scale; mt->spritexscale = player->mo->spritexscale; mt->spriteyscale = player->mo->spriteyscale; - memset(mt->args, 0, NUMMAPTHINGARGS*sizeof(*mt->args)); - memset(mt->stringargs, 0x00, NUMMAPTHINGSTRINGARGS*sizeof(*mt->stringargs)); - mt->pitch = mt->roll = 0; // Ignore offsets if (mt->type == MT_EMBLEM) diff --git a/src/m_cond.c b/src/m_cond.c index 5a5913297157e24ada893ca5fad77a4d38ffd3e3..e6edde3c6f5f38bc3c2623d36519dbf714ccfdfd 100644 --- a/src/m_cond.c +++ b/src/m_cond.c @@ -1,7 +1,7 @@ // SONIC ROBO BLAST 2 //----------------------------------------------------------------------------- // Copyright (C) 2012-2016 by Matthew "Kaito Sinclaire" Walsh. -// Copyright (C) 2012-2023 by Sonic Team Junior. +// Copyright (C) 2012-2024 by Sonic Team Junior. // // This program is free software distributed under the // terms of the GNU General Public License, version 2. @@ -443,6 +443,22 @@ UINT8 M_CompletionEmblems(gamedata_t *data) // Bah! Duplication sucks, but it's // ------------------- // Quick unlock checks // ------------------- +boolean M_CheckNetUnlockByID(UINT16 unlockid) +{ + if (unlockid >= MAXUNLOCKABLES + || !unlockables[unlockid].conditionset) + { + return true; // default permit + } + + if (netgame) + { + return serverGamedata->unlocked[unlockid]; + } + + return clientGamedata->unlocked[unlockid]; +} + UINT8 M_AnySecretUnlocked(gamedata_t *data) { INT32 i; diff --git a/src/m_cond.h b/src/m_cond.h index 2491a384c02aa5f34ba47933eba689811ac599d0..e09447d3c3246058e7cf642d00fa1eb08afa0e15 100644 --- a/src/m_cond.h +++ b/src/m_cond.h @@ -1,7 +1,7 @@ // SONIC ROBO BLAST 2 //----------------------------------------------------------------------------- // Copyright (C) 2012-2016 by Matthew "Kaito Sinclaire" Walsh. -// Copyright (C) 2012-2023 by Sonic Team Junior. +// Copyright (C) 2012-2024 by Sonic Team Junior. // // This program is free software distributed under the // terms of the GNU General Public License, version 2. @@ -16,6 +16,10 @@ #include "doomdef.h" #include "doomdata.h" +#ifdef __cplusplus +extern "C" { +#endif + // -------- // Typedefs // -------- @@ -249,6 +253,7 @@ UINT8 M_CompletionEmblems(gamedata_t *data); void M_SilentUpdateSkinAvailabilites(void); // Checking unlockable status +boolean M_CheckNetUnlockByID(UINT16 unlockid); UINT8 M_AnySecretUnlocked(gamedata_t *data); UINT8 M_SecretUnlocked(INT32 type, gamedata_t *data); UINT8 M_MapLocked(INT32 mapnum, gamedata_t *data); @@ -275,4 +280,8 @@ INT32 M_EmblemSkinNum(emblem_t *emblem); #define M_Achieved(a, data) ((a) >= MAXCONDITIONSETS || data->achieved[a]) +#ifdef __cplusplus +} // extern "C" +#endif + #endif diff --git a/src/m_misc.h b/src/m_misc.h index 04ac66ca65e1ff92d44172b12e1186cdaa04b648..875e10c6d3508df15f48072f111ac2065c38a59e 100644 --- a/src/m_misc.h +++ b/src/m_misc.h @@ -2,7 +2,7 @@ //----------------------------------------------------------------------------- // Copyright (C) 1993-1996 by id Software, Inc. // Copyright (C) 1998-2000 by DooM Legacy Team. -// Copyright (C) 1999-2023 by Sonic Team Junior. +// Copyright (C) 1999-2024 by Sonic Team Junior. // // This program is free software distributed under the // terms of the GNU General Public License, version 2. @@ -21,6 +21,10 @@ #include "d_event.h" // Screenshot responder #include "command.h" +#ifdef __cplusplus +extern "C" { +#endif + typedef enum { MM_OFF = 0, MM_APNG, @@ -124,4 +128,8 @@ int M_RoundUp(double number); #include "w_wad.h" extern char configfile[MAX_WADPATH]; +#ifdef __cplusplus +} // extern "C" +#endif + #endif diff --git a/src/m_perfstats.c b/src/m_perfstats.c index b9948bdc0c3284a3473e7e6e8b95b2e3bd8fb568..3a18376a82970fa48274e0c595cb7e950d52da72 100644 --- a/src/m_perfstats.c +++ b/src/m_perfstats.c @@ -1,6 +1,6 @@ // SONIC ROBO BLAST 2 //----------------------------------------------------------------------------- -// Copyright (C) 2020-2023 by Sonic Team Junior. +// Copyright (C) 2020-2024 by Sonic Team Junior. // // This program is free software distributed under the // terms of the GNU General Public License, version 2. @@ -71,6 +71,8 @@ ps_metric_t ps_lua_postthinkframe_time = {0}; ps_metric_t ps_lua_mobjhooks = {0}; +ps_metric_t ps_acs_time = {0}; + ps_metric_t ps_otherlogictime = {0}; // Columns for perfstats pages. @@ -163,6 +165,7 @@ perfstatrow_t gamelogic_rows[] = { {" lprethinkf", " LUAh_PreThinkFrame:", &ps_lua_prethinkframe_time, PS_TIME|PS_LEVEL}, {" lthinkf", " LUAh_ThinkFrame:", &ps_lua_thinkframe_time, PS_TIME|PS_LEVEL}, {" lpostthinkf", " LUAh_PostThinkFrame:", &ps_lua_postthinkframe_time, PS_TIME|PS_LEVEL}, + {" acstick", " ACS_Tick:", &ps_acs_time, PS_TIME|PS_LEVEL}, {" other ", " Other: ", &ps_otherlogictime, PS_TIME|PS_LEVEL}, {0} }; @@ -629,7 +632,8 @@ void PS_UpdateTickStats(void) ps_thinkertime.value.p - ps_lua_prethinkframe_time.value.p - ps_lua_thinkframe_time.value.p - - ps_lua_postthinkframe_time.value.p; + ps_lua_postthinkframe_time.value.p - + ps_acs_time.value.p; PS_CountThinkers(); } diff --git a/src/m_perfstats.h b/src/m_perfstats.h index 592ab31d2d62adac2704b740e40f5476158e38c6..37d1d218615e5c2a4de0ab8a44b00a4536e81599 100644 --- a/src/m_perfstats.h +++ b/src/m_perfstats.h @@ -1,6 +1,6 @@ // SONIC ROBO BLAST 2 //----------------------------------------------------------------------------- -// Copyright (C) 2020-2023 by Sonic Team Junior. +// Copyright (C) 2020-2024 by Sonic Team Junior. // // This program is free software distributed under the // terms of the GNU General Public License, version 2. @@ -48,6 +48,8 @@ extern ps_metric_t ps_lua_thinkframe_time; extern ps_metric_t ps_lua_postthinkframe_time; extern ps_metric_t ps_lua_mobjhooks; +extern ps_metric_t ps_acs_time; + extern ps_metric_t ps_otherlogictime; void PS_SetPreThinkFrameHookInfo(int index, precise_t time_taken, char* short_src); diff --git a/src/m_random.h b/src/m_random.h index a7c07a46b5e2c951548d67f409287e80345c20dd..274ee1315aa90d776b77b0c1069e243c402c171b 100644 --- a/src/m_random.h +++ b/src/m_random.h @@ -4,7 +4,7 @@ // Copyright (C) 1998-2000 by DooM Legacy Team. // Copyright (C) 2012-2016 by Matthew "Kaito Sinclaire" Walsh. // Copyright (C) 2022-2023 by tertu marybig. -// Copyright (C) 1999-2023 by Sonic Team Junior. +// Copyright (C) 1999-2024 by Sonic Team Junior. // // This program is free software distributed under the // terms of the GNU General Public License, version 2. @@ -19,8 +19,11 @@ #include "doomtype.h" #include "m_fixed.h" -//#define DEBUGRANDOM +#ifdef __cplusplus +extern "C" { +#endif +//#define DEBUGRANDOM // M_Random functions pull random numbers of various types that aren't network synced. // P_Random functions pulls random bytes from a PRNG that is network synced. @@ -75,4 +78,8 @@ void P_SetRandSeed(UINT32 seed); #endif UINT32 M_RandomizedSeed(void); +#ifdef __cplusplus +} // extern "C" +#endif + #endif diff --git a/src/netcode/gamestate.c b/src/netcode/gamestate.c index f36347c6d88e94a444fb186937d2672cfe3a0df9..afad8a9a66c1a08a97f9cc3ef82344b6d78386ee 100644 --- a/src/netcode/gamestate.c +++ b/src/netcode/gamestate.c @@ -1,7 +1,7 @@ // SONIC ROBO BLAST 2 //----------------------------------------------------------------------------- // Copyright (C) 1998-2000 by DooM Legacy Team. -// Copyright (C) 1999-2023 by Sonic Team Junior. +// Copyright (C) 1999-2024 by Sonic Team Junior. // // This program is free software distributed under the // terms of the GNU General Public License, version 2. @@ -67,7 +67,10 @@ void SV_SendSaveGame(INT32 node, boolean resending) } // Leave room for the uncompressed length. - save_p = savebuffer + sizeof(UINT32); + save_length = SAVEGAMESIZE; + save_start = savebuffer; + save_end = savebuffer + save_length; + save_p = save_start + sizeof(UINT32); P_SaveNetGame(resending); @@ -75,7 +78,6 @@ void SV_SendSaveGame(INT32 node, boolean resending) if (length > SAVEGAMESIZE) { free(savebuffer); - save_p = NULL; I_Error("Savegame buffer overrun"); } @@ -112,7 +114,8 @@ void SV_SendSaveGame(INT32 node, boolean resending) } AddRamToSendQueue(node, buffertosend, length, SF_RAM, 0); - save_p = NULL; + save_p = save_start = save_end = NULL; + save_length = 0; // Remember when we started sending the savegame so we can handle timeouts netnodes[node].sendingsavegame = true; @@ -184,7 +187,10 @@ void CL_LoadReceivedSavegame(boolean reloading) return; } - save_p = savebuffer; + save_length = length; + save_start = savebuffer; + save_end = save_start + save_length; + save_p = save_start; // Decompress saved game if necessary. decompressedlen = READUINT32(save_p); @@ -193,7 +199,11 @@ void CL_LoadReceivedSavegame(boolean reloading) UINT8 *decompressedbuffer = Z_Malloc(decompressedlen, PU_STATIC, NULL); lzf_decompress(save_p, length - sizeof(UINT32), decompressedbuffer, decompressedlen); Z_Free(savebuffer); - save_p = savebuffer = decompressedbuffer; + + save_length = decompressedlen; + save_start = decompressedbuffer; + save_end = save_start + save_length; + save_p = savebuffer = save_start; } paused = false; @@ -220,7 +230,8 @@ void CL_LoadReceivedSavegame(boolean reloading) // done Z_Free(savebuffer); - save_p = NULL; + save_p = save_start = save_end = NULL; + save_length = 0; if (unlink(tmpsave) == -1) CONS_Alert(CONS_ERROR, M_GetText("Can't delete %s\n"), tmpsave); consistancy[gametic%BACKUPTICS] = Consistancy(); diff --git a/src/netcode/net_command.h b/src/netcode/net_command.h index a0c46f3a2ffb26156708bfe3fbb3244b5fbc48ec..783e63efaaacf0c5d0dc1f37e139779d96b08b51 100644 --- a/src/netcode/net_command.h +++ b/src/netcode/net_command.h @@ -1,7 +1,7 @@ // SONIC ROBO BLAST 2 //----------------------------------------------------------------------------- // Copyright (C) 1998-2000 by DooM Legacy Team. -// Copyright (C) 1999-2022 by Sonic Team Junior. +// Copyright (C) 1999-2024 by Sonic Team Junior. // // This program is free software distributed under the // terms of the GNU General Public License, version 2. @@ -14,8 +14,13 @@ #define __D_NET_COMMAND__ #include "d_clisrv.h" + #include "../doomtype.h" +#ifdef __cplusplus +extern "C" { +#endif + // Must be a power of two #define TEXTCMD_HASH_SIZE 4 @@ -63,4 +68,8 @@ void CL_SendNetCommands(void); void SendKick(UINT8 playernum, UINT8 msg); void SendKicksForNode(SINT8 node, UINT8 msg); +#ifdef __cplusplus +} // extern "C" +#endif + #endif diff --git a/src/p_enemy.c b/src/p_enemy.c index 4990db6fd50a2f96102056984b4d02e3c0205ddb..46225572429dd9153eb9e7ff71345e813e260e44 100644 --- a/src/p_enemy.c +++ b/src/p_enemy.c @@ -2,7 +2,7 @@ //----------------------------------------------------------------------------- // Copyright (C) 1993-1996 by id Software, Inc. // Copyright (C) 1998-2000 by DooM Legacy Team. -// Copyright (C) 1999-2023 by Sonic Team Junior. +// Copyright (C) 1999-2024 by Sonic Team Junior. // // This program is free software distributed under the // terms of the GNU General Public License, version 2. @@ -8651,7 +8651,7 @@ void A_LinedefExecuteFromArg(mobj_t *actor) if (!actor->spawnpoint) return; - if (locvar1 < 0 || locvar1 > NUMMAPTHINGARGS) + if (locvar1 < 0 || locvar1 > NUM_MAPTHING_ARGS) { CONS_Debug(DBG_GAMELOGIC, "A_LinedefExecuteFromArg: Invalid mapthing arg %d\n", locvar1); return; diff --git a/src/p_inter.c b/src/p_inter.c index e73cd1fce675ee17721e6236e7dc764ede8e9d69..f8923c7a111a6a6b4525eca9d191bec31b6ef5d3 100644 --- a/src/p_inter.c +++ b/src/p_inter.c @@ -2,7 +2,7 @@ //----------------------------------------------------------------------------- // Copyright (C) 1993-1996 by id Software, Inc. // Copyright (C) 1998-2000 by DooM Legacy Team. -// Copyright (C) 1999-2023 by Sonic Team Junior. +// Copyright (C) 1999-2024 by Sonic Team Junior. // // This program is free software distributed under the // terms of the GNU General Public License, version 2. @@ -22,6 +22,7 @@ #include "st_stuff.h" #include "hu_stuff.h" #include "lua_hook.h" +#include "acs/interface.h" #include "m_cond.h" // unlockables, emblems, etc #include "p_setup.h" #include "m_cheat.h" // objectplace @@ -2543,6 +2544,8 @@ void P_KillMobj(mobj_t *target, mobj_t *inflictor, mobj_t *source, UINT8 damaget if (LUA_HookMobjDeath(target, inflictor, source, damagetype) || P_MobjWasRemoved(target)) return; + P_ActivateThingSpecial(target, source); + // Let EVERYONE know what happened to a player! 01-29-2002 Tails if (target->player && !target->player->spectator) { @@ -2780,6 +2783,8 @@ void P_KillMobj(mobj_t *target, mobj_t *inflictor, mobj_t *source, UINT8 damaget } } } + + ACS_RunPlayerDeathScript(target->player); } if (source && target && target->player && source->player) diff --git a/src/p_local.h b/src/p_local.h index 249c3cd4b6de5248140c00e2dfc2332081a5d00e..54c9fabb8ae1da2cb291b620f7231cace70f0fe7 100644 --- a/src/p_local.h +++ b/src/p_local.h @@ -2,7 +2,7 @@ //----------------------------------------------------------------------------- // Copyright (C) 1993-1996 by id Software, Inc. // Copyright (C) 1998-2000 by DooM Legacy Team. -// Copyright (C) 1999-2023 by Sonic Team Junior. +// Copyright (C) 1999-2024 by Sonic Team Junior. // // This program is free software distributed under the // terms of the GNU General Public License, version 2. @@ -23,6 +23,10 @@ #include "r_defs.h" #include "p_maputl.h" +#ifdef __cplusplus +extern "C" { +#endif + #define FLOATSPEED (FRACUNIT*4) // Maximum player score. @@ -74,6 +78,7 @@ extern thinker_t thlist[]; extern mobj_t *mobjcache; void P_InitThinkers(void); +void P_InvalidateThinkersWithoutInit(void); void P_AddThinker(const thinklistnum_t n, thinker_t *thinker); void P_RemoveThinker(thinker_t *thinker); @@ -152,6 +157,7 @@ UINT16 P_GetPlayerColor(player_t *player); boolean P_IsObjectInGoop(mobj_t *mo); boolean P_IsObjectOnGround(mobj_t *mo); boolean P_InSpaceSector(mobj_t *mo); +#define P_IsObjectFlipped(o) (((o)->eflags & MFE_VERTICALFLIP) == MFE_VERTICALFLIP) boolean P_InQuicksand(mobj_t *mo); boolean P_InJumpFlipSector(mobj_t *mo); boolean P_PlayerHitFloor(player_t *player, boolean dorollstuff); @@ -209,6 +215,8 @@ void P_DoSpinDashDust(player_t *player); #define P_AnalogMove(player) (P_ControlStyle(player) == CS_LMAOGALOG) boolean P_TransferToNextMare(player_t *player); UINT8 P_FindLowestMare(void); +UINT8 P_FindLowestLap(void); +UINT8 P_FindHighestLap(void); void P_FindEmerald(void); void P_TransferToAxis(player_t *player, INT32 axisnum); boolean P_PlayerMoving(INT32 pnum); @@ -553,5 +561,18 @@ void P_DoSuperDetransformation(player_t *player); void P_ExplodeMissile(mobj_t *mo); void P_CheckGravity(mobj_t *mo, boolean affect); void P_SetPitchRollFromSlope(mobj_t *mo, pslope_t *slope); +fixed_t P_GetMobjHead(mobj_t *mo); +fixed_t P_GetMobjFeet(mobj_t *mo); + +void P_InitTIDHash(void); +void P_SetThingTID(mobj_t *mo, mtag_t tid); +void P_RemoveThingTID(mobj_t *mo); +mobj_t *P_FindMobjFromTID(mtag_t tid, mobj_t *i, mobj_t *activator); + +void P_DeleteMobjStringArgs(mobj_t *mobj); + +#ifdef __cplusplus +} // extern "C" +#endif #endif // __P_LOCAL__ diff --git a/src/p_map.c b/src/p_map.c index f97ddfa3cd8aa3fa9f7e9dbb3e6d76b041aca164..8d98f8b6b3c45583df1e08fa10558dfc322c67f5 100644 --- a/src/p_map.c +++ b/src/p_map.c @@ -2,7 +2,7 @@ //----------------------------------------------------------------------------- // Copyright (C) 1993-1996 by id Software, Inc. // Copyright (C) 1998-2000 by DooM Legacy Team. -// Copyright (C) 1999-2023 by Sonic Team Junior. +// Copyright (C) 1999-2024 by Sonic Team Junior. // // This program is free software distributed under the // terms of the GNU General Public License, version 2. @@ -64,6 +64,19 @@ line_t *ceilingline; // that is, for any line which is 'solid' line_t *blockingline; +// Mostly re-ported from DOOM Legacy +// Keep track of special lines as they are hit, process them when the move is valid +static size_t *spechit = NULL; +static size_t spechit_max = 0U; +static size_t numspechit = 0U; + +// Need a intermediate buffer for P_TryMove because it performs multiple moves +// the lines put into spechit will be moved into here after each checkposition, +// then and duplicates will be removed before processing +static size_t *spechitint = NULL; +static size_t spechitint_max = 0U; +static size_t numspechitint = 0U; + msecnode_t *sector_list = NULL; mprecipsecnode_t *precipsector_list = NULL; camera_t *mapcampointer; @@ -77,6 +90,11 @@ camera_t *mapcampointer; // static boolean P_TeleportMove(mobj_t *thing, fixed_t x, fixed_t y, fixed_t z) { + boolean startingonground = P_IsObjectOnGround(thing); + sector_t *oldsector = thing->subsector->sector; + + numspechit = 0U; + // the move is ok, // so link the thing into its new position P_UnsetThingPosition(thing); @@ -104,6 +122,8 @@ static boolean P_TeleportMove(mobj_t *thing, fixed_t x, fixed_t y, fixed_t z) thing->floorrover = tmfloorrover; thing->ceilingrover = tmceilingrover; + P_CheckSectorTransitionalEffects(thing, oldsector, startingonground); + return true; } @@ -135,6 +155,92 @@ boolean P_MoveOrigin(mobj_t *thing, fixed_t x, fixed_t y, fixed_t z) // MOVEMENT ITERATOR FUNCTIONS // ========================================================================= +// For our intermediate buffer, remove any duplicate entries by adding each one to +// a temprary buffer if it's not already in there, copy the temporary buffer back over the intermediate afterwards +static void spechitint_removedups(void) +{ + // Only needs to be run if there's more than 1 line crossed + if (numspechitint > 1U) + { + boolean valueintemp = false; + size_t i = 0U, j = 0U; + size_t numspechittemp = 0U; + size_t *spechittemp = Z_Calloc(numspechitint * sizeof(size_t), PU_STATIC, NULL); + + // Fill the hashtable + for (i = 0U; i < numspechitint; i++) + { + valueintemp = false; + for (j = 0; j < numspechittemp; j++) + { + if (spechitint[i] == spechittemp[j]) + { + valueintemp = true; + break; + } + } + + if (!valueintemp) + { + spechittemp[numspechittemp] = spechitint[i]; + numspechittemp++; + } + } + + // The hash table now IS the result we want to send back + // easiest way to handle this is a memcpy + if (numspechittemp != numspechitint) + { + memcpy(spechitint, spechittemp, numspechittemp * sizeof(size_t)); + numspechitint = numspechittemp; + } + + Z_Free(spechittemp); + } +} + +// copy the contents of spechit into the end of spechitint +static void spechitint_copyinto(void) +{ + if (numspechit > 0U) + { + if (numspechitint + numspechit >= spechitint_max) + { + spechitint_max = spechitint_max + numspechit; + spechitint = Z_Realloc(spechitint, spechitint_max * sizeof(size_t), PU_STATIC, NULL); + } + + memcpy(&spechitint[numspechitint], spechit, numspechit * sizeof(size_t)); + numspechitint += numspechit; + } +} + +static void add_spechit(line_t *ld) +{ + if (numspechit >= spechit_max) + { + spechit_max = spechit_max ? spechit_max * 2U : 16U; + spechit = Z_Realloc(spechit, spechit_max * sizeof(size_t), PU_STATIC, NULL); + } + + spechit[numspechit] = ld - lines; + numspechit++; +} + +static boolean P_SpecialIsLinedefCrossType(line_t *ld) +{ + boolean linedefcrossspecial = false; + + // Take anything with any cross type for now, + // we'll have to filter it down later... + if (ld->activation & (SPAC_CROSS | SPAC_CROSSMONSTER | SPAC_CROSSMISSILE)) + { + linedefcrossspecial = P_CanActivateSpecial(ld->special); + } + + return linedefcrossspecial; +} + // P_DoSpring // // MF_SPRING does some weird, mildly hacky stuff sometimes. @@ -2015,6 +2121,12 @@ static boolean PIT_CheckLine(line_t *ld) if (lowfloor < tmdropoffz) tmdropoffz = lowfloor; + // we've crossed the line + if (P_SpecialIsLinedefCrossType(ld)) + { + add_spechit(ld); + } + return true; } @@ -2682,6 +2794,9 @@ increment_move fixed_t thingtop; floatok = false; + // reset this to 0 at the start of each trymove call as it's only used here + numspechitint = 0U; + // This makes sure that there are no freezes from computing extremely small movements. // Originally was MAXRADIUS/2, but that can cause some bad inconsistencies for small players. radius = max(radius, thing->scale); @@ -2721,6 +2836,9 @@ increment_move if (!P_CheckPosition(thing, tryx, tryy) || P_MobjWasRemoved(thing)) return false; // solid wall or thing + // copy into the spechitint buffer from spechit + spechitint_copyinto(); + if (!(thing->flags & MF_NOCLIP)) { //All things are affected by their scale. @@ -2860,7 +2978,10 @@ boolean P_CheckMove(mobj_t *thing, fixed_t x, fixed_t y, boolean allowdropoff) // boolean P_TryMove(mobj_t *thing, fixed_t x, fixed_t y, boolean allowdropoff) { + fixed_t oldx = thing->x; + fixed_t oldy = thing->y; fixed_t startingonground = P_IsObjectOnGround(thing); + sector_t *oldsector = thing->subsector->sector; // The move is ok! if (!increment_move(thing, x, y, allowdropoff)) @@ -2936,6 +3057,29 @@ boolean P_TryMove(mobj_t *thing, fixed_t x, fixed_t y, boolean allowdropoff) thing->eflags |= MFE_ONGROUND; P_SetThingPosition(thing); + + P_CheckSectorTransitionalEffects(thing, oldsector, startingonground); + + // remove any duplicates that may be in spechitint + spechitint_removedups(); + + // handle any of the special lines that were crossed + if (!(thing->flags & (MF_NOCLIP))) + { + line_t *ld = NULL; + INT32 side = 0, oldside = 0; + while (numspechitint--) + { + ld = &lines[spechitint[numspechitint]]; + side = P_PointOnLineSide(thing->x, thing->y, ld); + oldside = P_PointOnLineSide(oldx, oldy, ld); + if (side != oldside) + { + P_CrossSpecialLine(ld, oldside, thing); + } + } + } + return true; } diff --git a/src/p_mobj.c b/src/p_mobj.c index 9cdd2628db8cfec3ee8af83a6f96b3713879b164..2f04b17f7b7e6e65be039e4202c42ee36b1f6c24 100644 --- a/src/p_mobj.c +++ b/src/p_mobj.c @@ -2,7 +2,7 @@ //----------------------------------------------------------------------------- // Copyright (C) 1993-1996 by id Software, Inc. // Copyright (C) 1998-2000 by DooM Legacy Team. -// Copyright (C) 1999-2023 by Sonic Team Junior. +// Copyright (C) 1999-2024 by Sonic Team Junior. // // This program is free software distributed under the // terms of the GNU General Public License, version 2. @@ -1748,7 +1748,10 @@ void P_XYMovement(mobj_t *mo) } else if (P_MobjWasRemoved(mo)) return; - else if (mo->flags & MF_BOUNCE) + + P_PushSpecialLine(blockingline, mo); + + if (mo->flags & MF_BOUNCE) { P_BounceMove(mo); xmove = ymove = 0; @@ -10169,6 +10172,12 @@ void P_MobjThinker(mobj_t *mobj) tmfloorthing = tmhitthing = NULL; + if (udmf) + { + // Check for continuous sector special actions + P_CheckMobjTouchingSectorActions(mobj, true, true); + } + // Sector flag MSF_TRIGGERLINE_MOBJ allows ANY mobj to trigger a linedef exec P_CheckMobjTrigger(mobj, false); @@ -11252,6 +11261,8 @@ void P_RemoveMobj(mobj_t *mobj) if (mobj->spawnpoint) mobj->spawnpoint->mobj = NULL; + P_RemoveThingTID(mobj); + P_DeleteMobjStringArgs(mobj); R_RemoveMobjInterpolator(mobj); // free block @@ -13459,6 +13470,65 @@ static void P_SetAmbush(mapthing_t *mthing, mobj_t *mobj) mobj->flags2 |= MF2_AMBUSH; } +void P_CopyMapThingSpecialFieldsToMobj(const mapthing_t *mthing, mobj_t *mobj) +{ + size_t arg = SIZE_MAX; + + P_SetThingTID(mobj, mthing->tid); + + mobj->special = mthing->special; + + for (arg = 0; arg < NUM_MAPTHING_ARGS; arg++) + { + mobj->thing_args[arg] = mthing->args[arg]; + } + + for (arg = 0; arg < NUM_MAPTHING_STRINGARGS; arg++) + { + size_t len = 0; + + if (mthing->stringargs[arg]) + { + len = strlen(mthing->stringargs[arg]); + } + + if (len == 0) + { + Z_Free(mobj->thing_stringargs[arg]); + mobj->thing_stringargs[arg] = NULL; + continue; + } + + mobj->thing_stringargs[arg] = Z_Realloc(mobj->thing_stringargs[arg], len + 1, PU_LEVEL, NULL); + M_Memcpy(mobj->thing_stringargs[arg], mthing->stringargs[arg], len + 1); + } + + for (arg = 0; arg < NUM_SCRIPT_ARGS; arg++) + { + mobj->script_args[arg] = mthing->script_args[arg]; + } + + for (arg = 0; arg < NUM_SCRIPT_STRINGARGS; arg++) + { + size_t len = 0; + + if (mthing->script_stringargs[arg]) + { + len = strlen(mthing->script_stringargs[arg]); + } + + if (len == 0) + { + Z_Free(mobj->script_stringargs[arg]); + mobj->script_stringargs[arg] = NULL; + continue; + } + + mobj->script_stringargs[arg] = Z_Realloc(mobj->script_stringargs[arg], len + 1, PU_LEVEL, NULL); + M_Memcpy(mobj->script_stringargs[arg], mthing->script_stringargs[arg], len + 1); + } +} + static mobj_t *P_SpawnMobjFromMapThing(mapthing_t *mthing, fixed_t x, fixed_t y, fixed_t z, mobjtype_t i) { mobj_t *mobj = NULL; @@ -13476,6 +13546,8 @@ static mobj_t *P_SpawnMobjFromMapThing(mapthing_t *mthing, fixed_t x, fixed_t y, mobj->spritexscale = mthing->spritexscale; mobj->spriteyscale = mthing->spriteyscale; + P_CopyMapThingSpecialFieldsToMobj(mthing, mobj); + if (!P_SetupSpawnedMapThing(mthing, mobj, &doangle)) return mobj; @@ -14350,3 +14422,150 @@ mobj_t *P_SpawnMobjFromMobj(mobj_t *mobj, fixed_t xofs, fixed_t yofs, fixed_t zo return newmobj; } + +// +// P_GetMobjHead & P_GetMobjFeet +// Returns the top and bottom of an object, follows appearance, not physics, +// in reverse gravity. +// + +fixed_t P_GetMobjHead(mobj_t *mobj) +{ + return P_IsObjectFlipped(mobj) ? mobj->z : mobj->z + mobj->height; +} + +fixed_t P_GetMobjFeet(mobj_t *mobj) +{ + /* + / | + / | + /--\-----/ | + ( ( ( ( | + \--------------/ + */ + + return P_IsObjectFlipped(mobj) ? mobj->z + mobj->height : mobj->z; +} + +// +// Thing IDs / tags +// +// TODO: Replace this system with taglist_t instead. +// The issue is that those require a static numbered ID +// to determine which struct it belongs to, which mobjs +// don't really have. +// + +#define TID_HASH_CHAINS (131) +static mobj_t *TID_Hash[TID_HASH_CHAINS]; + +// +// P_InitTIDHash +// Initializes mobj tag hash array +// +void P_InitTIDHash(void) +{ + memset(TID_Hash, 0, TID_HASH_CHAINS * sizeof(mobj_t *)); +} + +// +// P_SetThingTID +// Adds a mobj to the hash array +// +void P_SetThingTID(mobj_t *mo, mtag_t tid) +{ + INT32 key = 0; + + if (tid == 0) + { + if (mo->tid != 0) + { + P_RemoveThingTID(mo); + } + + return; + } + + mo->tid = tid; + + // Insert at the head of this chain + key = tid % TID_HASH_CHAINS; + + mo->tid_next = TID_Hash[key]; + mo->tid_prev = &TID_Hash[key]; + TID_Hash[key] = mo; + + // Connect to any existing things in chain + if (mo->tid_next != NULL) + { + mo->tid_next->tid_prev = &(mo->tid_next); + } +} + +// +// P_RemoveThingTID +// Removes a mobj from the hash array +// +void P_RemoveThingTID(mobj_t *mo) +{ + if (mo->tid != 0 && mo->tid_prev != NULL) + { + // Fix the gap this would leave. + *(mo->tid_prev) = mo->tid_next; + + if (mo->tid_next != NULL) + { + mo->tid_next->tid_prev = mo->tid_prev; + } + } + + // Remove TID. + mo->tid = 0; +} + +// +// P_FindMobjFromTID +// Mobj tag search function. +// +mobj_t *P_FindMobjFromTID(mtag_t tid, mobj_t *i, mobj_t *activator) +{ + if (tid == 0) + { + // 0 grabs the activator, if applicable, + // for some ACS functions. + + if (i != NULL) + { + // Don't do more than once. + return NULL; + } + + return activator; + } + + i = (i != NULL) ? i->tid_next : TID_Hash[tid % TID_HASH_CHAINS]; + + while (i != NULL && i->tid != tid) + { + i = i->tid_next; + } + + return i; +} + +void P_DeleteMobjStringArgs(mobj_t *mobj) +{ + size_t i = SIZE_MAX; + + for (i = 0; i < NUM_MAPTHING_STRINGARGS; i++) + { + Z_Free(mobj->thing_stringargs[i]); + mobj->thing_stringargs[i] = NULL; + } + + for (i = 0; i < NUM_SCRIPT_STRINGARGS; i++) + { + Z_Free(mobj->script_stringargs[i]); + mobj->script_stringargs[i] = NULL; + } +} diff --git a/src/p_mobj.h b/src/p_mobj.h index 2f013a2f30fac7f7d117cb190fcbc5bd1db17e54..47d22b97a68e4dd9539aa3a78eccfc2d9364b9d5 100644 --- a/src/p_mobj.h +++ b/src/p_mobj.h @@ -2,7 +2,7 @@ //----------------------------------------------------------------------------- // Copyright (C) 1993-1996 by id Software, Inc. // Copyright (C) 1998-2000 by DooM Legacy Team. -// Copyright (C) 1999-2023 by Sonic Team Junior. +// Copyright (C) 1999-2024 by Sonic Team Junior. // // This program is free software distributed under the // terms of the GNU General Public License, version 2. @@ -28,6 +28,10 @@ // Needs precompiled tables/data structures. #include "info.h" +#ifdef __cplusplus +extern "C" { +#endif + // // NOTES: mobj_t // @@ -343,6 +347,10 @@ typedef struct mobj_s UINT32 flags2; // MF2_ flags UINT16 eflags; // extra flags + mtag_t tid; + struct mobj_s *tid_next; + struct mobj_s **tid_prev; // killough 8/11/98: change to ptr-to-ptr + void *skin; // overrides 'sprite' when non-NULL (for player bodies to 'remember' the skin) // Player and mobj sprites in multiplayer modes are modified @@ -420,6 +428,13 @@ typedef struct mobj_s fixed_t shadowscale; // If this object casts a shadow, and the size relative to radius INT32 dispoffset; // copy of info->dispoffset, so mobjs can be sorted independently of their type + INT32 thing_args[NUM_MAPTHING_ARGS]; + char *thing_stringargs[NUM_MAPTHING_STRINGARGS]; + + INT16 special; + INT32 script_args[NUM_SCRIPT_ARGS]; + char *script_stringargs[NUM_SCRIPT_STRINGARGS]; + // WARNING: New fields must be added separately to savegame and Lua. } mobj_t; @@ -512,6 +527,7 @@ fixed_t P_GetMobjSpawnHeight(const mobjtype_t mobjtype, const fixed_t x, const f fixed_t P_GetMapThingSpawnHeight(const mobjtype_t mobjtype, const mapthing_t* mthing, const fixed_t x, const fixed_t y); mobj_t *P_SpawnMapThing(mapthing_t *mthing); +void P_CopyMapThingSpecialFieldsToMobj(const mapthing_t *mthing, mobj_t *mobj); void P_SpawnHoop(mapthing_t *mthing); void P_SetBonusTime(mobj_t *mobj); void P_SpawnItemPattern(mapthing_t *mthing, boolean bonustime); @@ -547,4 +563,9 @@ extern UINT16 emeraldspawndelay; extern INT32 numstarposts; extern UINT16 bossdisabled; extern boolean stoppedclock; + +#ifdef __cplusplus +} // extern "C" +#endif + #endif diff --git a/src/p_polyobj.h b/src/p_polyobj.h index 0573f6350aec32814e2ca03fc58aaa0c0e8e4bb6..32e5827faed63f4f8d2c958c8c1cd8c466f0a2ff 100644 --- a/src/p_polyobj.h +++ b/src/p_polyobj.h @@ -1,7 +1,7 @@ // SONIC ROBO BLAST 2 //----------------------------------------------------------------------------- // Copyright (C) 2006 by James Haley -// Copyright (C) 2006-2023 by Sonic Team Junior. +// Copyright (C) 2006-2024 by Sonic Team Junior. // // This program is free software distributed under the // terms of the GNU General Public License, version 2. @@ -18,6 +18,10 @@ #include "p_mobj.h" #include "r_defs.h" +#ifdef __cplusplus +extern "C" { +#endif + // // Defines // @@ -411,6 +415,8 @@ extern polyobj_t *PolyObjects; extern INT32 numPolyObjects; extern polymaplink_t **polyblocklinks; // polyobject blockmap +#ifdef __cplusplus +} // extern "C" #endif -// EOF +#endif diff --git a/src/p_saveg.c b/src/p_saveg.c index 5e4d6d0760441e6bc94c6815824b8b7e1ab38c80..75185a8acf87889316581ad0ed547a26193922a1 100644 --- a/src/p_saveg.c +++ b/src/p_saveg.c @@ -2,7 +2,7 @@ //----------------------------------------------------------------------------- // Copyright (C) 1993-1996 by id Software, Inc. // Copyright (C) 1998-2000 by DooM Legacy Team. -// Copyright (C) 1999-2023 by Sonic Team Junior. +// Copyright (C) 1999-2024 by Sonic Team Junior. // // This program is free software distributed under the // terms of the GNU General Public License, version 2. @@ -34,10 +34,14 @@ #include "r_sky.h" #include "p_polyobj.h" #include "lua_script.h" +#include "acs/interface.h" #include "p_slopes.h" savedata_t savedata; UINT8 *save_p; +UINT8 *save_start; +UINT8 *save_end; +size_t save_length; // Block UINT32s to attempt to ensure that the correct data is // being sent and received @@ -879,6 +883,35 @@ static void P_NetUnArchiveWaypoints(void) #define SD_GRAVITY 0x01 #define SD_FLOORPORTAL 0x02 #define SD_CEILPORTAL 0x04 +#define SD_ACTION 0x08 +#define SD_ARGS 0x10 +#define SD_STRINGARGS 0x20 +#define SD_ACTIVATION 0x40 + +static boolean P_SectorArgsEqual(const sector_t *sc, const sector_t *spawnsc) +{ + UINT8 i; + for (i = 0; i < NUM_SCRIPT_ARGS; i++) + if (sc->args[i] != spawnsc->args[i]) + return false; + + return true; +} + +static boolean P_SectorStringArgsEqual(const sector_t *sc, const sector_t *spawnsc) +{ + UINT8 i; + for (i = 0; i < NUM_SCRIPT_STRINGARGS; i++) + { + if (!sc->stringargs[i]) + return !spawnsc->stringargs[i]; + + if (strcmp(sc->stringargs[i], spawnsc->stringargs[i])) + return false; + } + + return true; +} // diff1 flags #define LD_FLAG 0x01 @@ -893,6 +926,7 @@ static void P_NetUnArchiveWaypoints(void) // diff2 flags #define LD_EXECUTORDELAY 0x01 #define LD_TRANSFPORTAL 0x02 +#define LD_ACTIVATION 0x04 // sidedef flags enum @@ -925,7 +959,7 @@ enum static boolean P_AreArgsEqual(const line_t *li, const line_t *spawnli) { UINT8 i; - for (i = 0; i < NUMLINEARGS; i++) + for (i = 0; i < NUM_SCRIPT_ARGS; i++) if (li->args[i] != spawnli->args[i]) return false; @@ -935,7 +969,7 @@ static boolean P_AreArgsEqual(const line_t *li, const line_t *spawnli) static boolean P_AreStringArgsEqual(const line_t *li, const line_t *spawnli) { UINT8 i; - for (i = 0; i < NUMLINESTRINGARGS; i++) + for (i = 0; i < NUM_SCRIPT_STRINGARGS; i++) { if (!li->stringargs[i]) return !spawnli->stringargs[i]; @@ -1112,6 +1146,14 @@ static void ArchiveSectors(void) diff5 |= SD_FLOORPORTAL; if (ss->portal_ceiling != spawnss->portal_ceiling) diff5 |= SD_CEILPORTAL; + if (ss->action != spawnss->action) + diff5 |= SD_ACTION; + if (!P_SectorArgsEqual(ss, spawnss)) + diff5 |= SD_ARGS; + if (!P_SectorStringArgsEqual(ss, spawnss)) + diff5 |= SD_STRINGARGS; + if (ss->activation != spawnss->activation) + diff5 |= SD_ACTIVATION; if (ss->ffloors && CheckFFloorDiff(ss)) diff |= SD_FFLOORS; @@ -1210,6 +1252,33 @@ static void ArchiveSectors(void) WRITEUINT32(save_p, ss->portal_floor); if (diff5 & SD_CEILPORTAL) WRITEUINT32(save_p, ss->portal_ceiling); + if (diff5 & SD_ACTION) + WRITEINT16(save_p, ss->action); + if (diff5 & SD_ARGS) + { + for (j = 0; j < NUM_SCRIPT_ARGS; j++) + WRITEINT32(save_p, ss->args[j]); + } + if (diff5 & SD_STRINGARGS) + { + for (j = 0; j < NUM_SCRIPT_STRINGARGS; j++) + { + size_t len, k; + + if (!ss->stringargs[j]) + { + WRITEINT32(save_p, 0); + continue; + } + + len = strlen(ss->stringargs[j]); + WRITEINT32(save_p, len); + for (k = 0; k < len; k++) + WRITECHAR(save_p, ss->stringargs[j][k]); + } + } + if (diff5 & SD_ACTIVATION) + WRITEUINT32(save_p, ss->activation); if (diff & SD_FFLOORS) ArchiveFFloors(ss); } @@ -1347,6 +1416,35 @@ static void UnArchiveSectors(void) sectors[i].portal_floor = READUINT32(save_p); if (diff5 & SD_CEILPORTAL) sectors[i].portal_ceiling = READUINT32(save_p); + if (diff5 & SD_ACTION) + sectors[i].action = READINT16(save_p); + if (diff5 & SD_ARGS) + { + for (j = 0; j < NUM_SCRIPT_ARGS; j++) + sectors[i].args[j] = READINT32(save_p); + } + if (diff5 & SD_STRINGARGS) + { + for (j = 0; j < NUM_SCRIPT_STRINGARGS; j++) + { + size_t len = READINT32(save_p); + size_t k; + + if (!len) + { + Z_Free(sectors[i].stringargs[j]); + sectors[i].stringargs[j] = NULL; + continue; + } + + sectors[i].stringargs[j] = Z_Realloc(sectors[i].stringargs[j], len + 1, PU_LEVEL, NULL); + for (k = 0; k < len; k++) + sectors[i].stringargs[j][k] = READCHAR(save_p); + sectors[i].stringargs[j][len] = '\0'; + } + } + if (diff5 & SD_ACTIVATION) + sectors[i].activation = READUINT32(save_p); if (diff & SD_FFLOORS) UnArchiveFFloors(§ors[i]); @@ -1470,6 +1568,9 @@ static void ArchiveLines(void) if (li->secportal != spawnli->secportal) diff2 |= LD_TRANSFPORTAL; + if (li->activation != spawnli->activation) + diff2 |= LD_ACTIVATION; + if (li->sidenum[0] != NO_SIDEDEF) { side1diff = GetSideDiff(&sides[li->sidenum[0]], &spawnsides[li->sidenum[0]]); @@ -1501,13 +1602,13 @@ static void ArchiveLines(void) if (diff & LD_ARGS) { UINT8 j; - for (j = 0; j < NUMLINEARGS; j++) + for (j = 0; j < NUM_SCRIPT_ARGS; j++) WRITEINT32(save_p, li->args[j]); } if (diff & LD_STRINGARGS) { UINT8 j; - for (j = 0; j < NUMLINESTRINGARGS; j++) + for (j = 0; j < NUM_SCRIPT_STRINGARGS; j++) { size_t len, k; @@ -1531,6 +1632,8 @@ static void ArchiveLines(void) WRITEINT32(save_p, li->executordelay); if (diff2 & LD_TRANSFPORTAL) WRITEUINT32(save_p, li->secportal); + if (diff2 & LD_ACTIVATION) + WRITEUINT32(save_p, li->activation); } } WRITEUINT32(save_p, 0xffffffff); @@ -1609,13 +1712,13 @@ static void UnArchiveLines(void) if (diff & LD_ARGS) { UINT8 j; - for (j = 0; j < NUMLINEARGS; j++) + for (j = 0; j < NUM_SCRIPT_ARGS; j++) li->args[j] = READINT32(save_p); } if (diff & LD_STRINGARGS) { UINT8 j; - for (j = 0; j < NUMLINESTRINGARGS; j++) + for (j = 0; j < NUM_SCRIPT_STRINGARGS; j++) { size_t len = READINT32(save_p); size_t k; @@ -1641,6 +1744,8 @@ static void UnArchiveLines(void) li->executordelay = READINT32(save_p); if (diff2 & LD_TRANSFPORTAL) li->secportal = READUINT32(save_p); + if (diff2 & LD_ACTIVATION) + li->activation = READUINT32(save_p); } } @@ -1682,6 +1787,53 @@ static void P_NetUnArchiveWorld(void) // Thinkers // +static boolean P_ThingArgsEqual(const mobj_t *mobj, const mapthing_t *mapthing) +{ + UINT8 i; + for (i = 0; i < NUM_MAPTHING_ARGS; i++) + if (mobj->thing_args[i] != mapthing->args[i]) + return false; + + return true; +} + +static boolean P_ThingStringArgsEqual(const mobj_t *mobj, const mapthing_t *mapthing) +{ + UINT8 i; + for (i = 0; i < NUM_MAPTHING_STRINGARGS; i++) + { + if (!mobj->thing_stringargs[i]) + return !mapthing->stringargs[i]; + + if (strcmp(mobj->thing_stringargs[i], mapthing->stringargs[i])) + return false; + } + + return true; +} + +static boolean P_ThingScriptEqual(const mobj_t *mobj, const mapthing_t *mapthing) +{ + UINT8 i; + if (mobj->special != mapthing->special) + return false; + + for (i = 0; i < NUM_SCRIPT_ARGS; i++) + if (mobj->script_args[i] != mapthing->script_args[i]) + return false; + + for (i = 0; i < NUM_SCRIPT_STRINGARGS; i++) + { + if (!mobj->script_stringargs[i]) + return !mapthing->script_stringargs[i]; + + if (strcmp(mobj->script_stringargs[i], mapthing->script_stringargs[i])) + return false; + } + + return true; +} + typedef enum { MD_SPAWNPOINT = 1, @@ -1746,7 +1898,11 @@ typedef enum MD2_DISPOFFSET = 1<<23, MD2_DRAWONLYFORPLAYER = 1<<24, MD2_DONTDRAWFORVIEWMOBJ = 1<<25, - MD2_TRANSLATION = 1<<26 + MD2_TRANSLATION = 1<<26, + MD2_ARGS = 1<<27, + MD2_STRINGARGS = 1<<28, + MD2_TID = 1<<29, + MD2_SPECIAL = 1<<30 } mobj_diff2_t; typedef enum @@ -1843,6 +1999,8 @@ static void SaveMobjThinker(const thinker_t *th, const UINT8 type) if (mobj->type == MT_HOOPCENTER && mobj->threshold == 4242) return; + diff2 = 0; + if (mobj->spawnpoint && mobj->info->doomednum != -1) { // spawnpoint is not modified but we must save it since it is an identifier @@ -1857,11 +2015,61 @@ static void SaveMobjThinker(const thinker_t *th, const UINT8 type) if (mobj->info->doomednum != mobj->spawnpoint->type) diff |= MD_TYPE; + + if (!P_ThingArgsEqual(mobj, mobj->spawnpoint)) + diff2 |= MD2_ARGS; + + if (!P_ThingStringArgsEqual(mobj, mobj->spawnpoint)) + diff2 |= MD2_STRINGARGS; + + if (!P_ThingScriptEqual(mobj, mobj->spawnpoint)) + diff2 |= MD2_SPECIAL; } else + { diff = MD_POS | MD_TYPE; // not a map spawned thing so make it from scratch - diff2 = 0; + for (unsigned j = 0; j < NUM_MAPTHING_ARGS; j++) + { + if (mobj->thing_args[j] != 0) + { + diff2 |= MD2_ARGS; + break; + } + } + + for (unsigned j = 0; j < NUM_MAPTHING_STRINGARGS; j++) + { + if (mobj->thing_stringargs[j] != NULL) + { + diff2 |= MD2_STRINGARGS; + break; + } + } + + if (mobj->special != 0) + { + diff2 |= MD2_SPECIAL; + } + + for (unsigned j = 0; j < NUM_SCRIPT_ARGS; j++) + { + if (mobj->script_args[j] != 0) + { + diff2 |= MD2_SPECIAL; + break; + } + } + + for (unsigned j = 0; j < NUM_SCRIPT_STRINGARGS; j++) + { + if (mobj->script_stringargs[j] != NULL) + { + diff2 |= MD2_SPECIAL; + break; + } + } + } // not the default but the most probable if (mobj->momx != 0 || mobj->momy != 0 || mobj->momz != 0 || mobj->pmomz !=0) @@ -5039,6 +5247,32 @@ static inline boolean P_UnArchiveLuabanksAndConsistency(void) return true; } +static void DoACSArchive(void) +{ + savebuffer_t save; + save.buffer = save_start; + save.end = save_end; + save.p = save_p; + save.size = save_length; + + ACS_Archive(&save); + + save_p = save.p; +} + +static void DoACSUnArchive(void) +{ + savebuffer_t save; + save.buffer = save_start; + save.end = save_end; + save.p = save_p; + save.size = save_length; + + ACS_UnArchive(&save); + + save_p = save.p; +} + void P_SaveGame(INT16 mapnum) { P_ArchiveMisc(mapnum); @@ -5079,6 +5313,8 @@ void P_SaveNetGame(boolean resending) P_NetArchiveWaypoints(); P_NetArchiveSectorPortals(); } + + DoACSArchive(); LUA_Archive(); P_ArchiveLuabanksAndConsistency(); @@ -5122,6 +5358,8 @@ boolean P_LoadNetGame(boolean reloading) P_RelinkPointers(); P_FinishMobjs(); } + + DoACSUnArchive(); LUA_UnArchive(); // This is stupid and hacky, but maybe it'll work! diff --git a/src/p_saveg.h b/src/p_saveg.h index 545008e7efc6af656fe94864a4ebcf8ea28e5f27..b8e84daa8cab0824d16fb3cc4581021ba3e68549 100644 --- a/src/p_saveg.h +++ b/src/p_saveg.h @@ -2,7 +2,7 @@ //----------------------------------------------------------------------------- // Copyright (C) 1993-1996 by id Software, Inc. // Copyright (C) 1998-2000 by DooM Legacy Team. -// Copyright (C) 1999-2023 by Sonic Team Junior. +// Copyright (C) 1999-2024 by Sonic Team Junior. // // This program is free software distributed under the // terms of the GNU General Public License, version 2. @@ -14,8 +14,8 @@ #ifndef __P_SAVEG__ #define __P_SAVEG__ -#ifdef __GNUG__ -#pragma interface +#ifdef __cplusplus +extern "C" { #endif #define NEWSKINSAVES (INT16_MAX) // TODO: 2.3: Delete (Purely for backwards compatibility) @@ -30,6 +30,14 @@ boolean P_LoadNetGame(boolean reloading); mobj_t *P_FindNewPosition(UINT32 oldposition); +typedef struct +{ + UINT8 *buffer; + UINT8 *p; + UINT8 *end; + size_t size; +} savebuffer_t; + typedef struct { UINT8 skin; @@ -43,5 +51,12 @@ typedef struct extern savedata_t savedata; extern UINT8 *save_p; +extern UINT8 *save_start; +extern UINT8 *save_end; +extern size_t save_length; + +#ifdef __cplusplus +} // extern "C" +#endif #endif diff --git a/src/p_setup.c b/src/p_setup.c index 41487d702f265c7e5164c61be8d0b8f8c059aa38..67d9d56cb7d482d4a54393cccb203852ed30253d 100644 --- a/src/p_setup.c +++ b/src/p_setup.c @@ -73,6 +73,8 @@ #include "lua_script.h" #include "lua_hook.h" +#include "acs/interface.h" + #ifdef _WIN32 #include <malloc.h> #include <math.h> @@ -103,6 +105,7 @@ unsigned char mapmd5[16]; // boolean udmf; +static INT32 udmf_version; size_t numvertexes, numsegs, numsectors, numsubsectors, numnodes, numlines, numsides, nummapthings; vertex_t *vertexes; seg_t *segs; @@ -1076,6 +1079,11 @@ static void P_LoadSectors(UINT8 *data) ss->friction = ORIG_FRICTION; + ss->action = 0; + memset(ss->args, 0, NUM_SCRIPT_ARGS*sizeof(*ss->args)); + memset(ss->stringargs, 0x00, NUM_SCRIPT_STRINGARGS*sizeof(*ss->stringargs)); + ss->activation = 0; + P_InitializeSector(ss); } } @@ -1180,10 +1188,11 @@ static void P_LoadLinedefs(UINT8 *data) ld->flags = SHORT(mld->flags); ld->special = SHORT(mld->special); Tag_FSet(&ld->tags, SHORT(mld->tag)); - memset(ld->args, 0, NUMLINEARGS*sizeof(*ld->args)); - memset(ld->stringargs, 0x00, NUMLINESTRINGARGS*sizeof(*ld->stringargs)); + memset(ld->args, 0, NUM_SCRIPT_ARGS*sizeof(*ld->args)); + memset(ld->stringargs, 0x00, NUM_SCRIPT_STRINGARGS*sizeof(*ld->stringargs)); ld->alpha = FRACUNIT; ld->executordelay = 0; + ld->activation = 0; P_SetLinedefV1(i, (UINT16)SHORT(mld->v1)); P_SetLinedefV2(i, (UINT16)SHORT(mld->v2)); @@ -1461,6 +1470,10 @@ static void P_LoadSidedefs(UINT8 *data) case 459: // Control text prompt (named tag) case 461: // Spawns an object on the map based on texture offsets case 463: // Colorizes an object + case 475: // ACS_Execute + case 476: // ACS_ExecuteAlways + case 477: // ACS_Suspend + case 478: // ACS_Terminate { char process[8*3+1]; memset(process,0,8*3+1); @@ -1531,11 +1544,15 @@ static void P_LoadThings(UINT8 *data) mt->type = READUINT16(data); mt->options = READUINT16(data); mt->extrainfo = (UINT8)(mt->type >> 12); + mt->tid = 0; Tag_FSet(&mt->tags, 0); mt->scale = FRACUNIT; mt->spritexscale = mt->spriteyscale = FRACUNIT; - memset(mt->args, 0, NUMMAPTHINGARGS*sizeof(*mt->args)); - memset(mt->stringargs, 0x00, NUMMAPTHINGSTRINGARGS*sizeof(*mt->stringargs)); + memset(mt->args, 0, NUM_MAPTHING_ARGS*sizeof(*mt->args)); + memset(mt->stringargs, 0x00, NUM_MAPTHING_STRINGARGS*sizeof(*mt->stringargs)); + mt->special = 0; + memset(mt->script_args, 0, NUM_SCRIPT_ARGS*sizeof(*mt->script_args)); + memset(mt->script_stringargs, 0x00, NUM_SCRIPT_STRINGARGS*sizeof(*mt->script_stringargs)); mt->pitch = mt->roll = 0; mt->type &= 4095; @@ -1607,6 +1624,13 @@ static boolean TextmapCount(size_t size) vertexesPos[numvertexes++] = M_TokenizerGetEndPos(); else if (fastcmp(tkn, "sector")) sectorsPos[numsectors++] = M_TokenizerGetEndPos(); + else if (fastcmp(tkn, "version")) + { + tkn = M_TokenizerRead(0); + udmf_version = atoi(tkn); + if (udmf_version > UDMF_CURRENT_VERSION) + CONS_Alert(CONS_WARNING, "Map is intended for future UDMF version '%d', current supported version is '%d'. This map may have issues loading.\n", udmf_version, UDMF_CURRENT_VERSION); + } else CONS_Alert(CONS_NOTICE, "Unknown field '%s'.\n", tkn); } @@ -1901,6 +1925,45 @@ static void ParseTextmapSectorParameter(UINT32 i, const char *param, const char if (fastcmp(val, "Mobj")) sectors[i].triggerer = TO_MOBJ; } + else if (fastcmp(param, "action")) + sectors[i].action = atol(val); + else if (fastncmp(param, "stringarg", 9) && strlen(param) > 9) + { + size_t argnum = atol(param + 9); + if (argnum >= NUM_SCRIPT_STRINGARGS) + return; + sectors[i].stringargs[argnum] = Z_Malloc(strlen(val) + 1, PU_LEVEL, NULL); + M_Memcpy(sectors[i].stringargs[argnum], val, strlen(val) + 1); + } + else if (fastncmp(param, "arg", 3) && strlen(param) > 3) + { + size_t argnum = atol(param + 3); + if (argnum >= NUM_SCRIPT_ARGS) + return; + sectors[i].args[argnum] = atol(val); + } + else if (fastcmp(param, "repeatspecial") && fastcmp("true", val)) + sectors[i].activation = (sectors[i].activation | ((sectors[i].activation & ~SECSPAC_TRIGGERMASK) | SECSPAC_REPEATSPECIAL)); + else if (fastcmp(param, "continuousspecial") && fastcmp("true", val)) + sectors[i].activation = (sectors[i].activation | ((sectors[i].activation & ~SECSPAC_TRIGGERMASK) | SECSPAC_CONTINUOUSSPECIAL)); + else if (fastcmp(param, "playerenter") && fastcmp("true", val)) + sectors[i].activation = (sectors[i].activation | SECSPAC_ENTER); + else if (fastcmp(param, "playerfloor") && fastcmp("true", val)) + sectors[i].activation = (sectors[i].activation | SECSPAC_FLOOR); + else if (fastcmp(param, "playerceiling") && fastcmp("true", val)) + sectors[i].activation = (sectors[i].activation | SECSPAC_CEILING); + else if (fastcmp(param, "monsterenter") && fastcmp("true", val)) + sectors[i].activation = (sectors[i].activation | SECSPAC_ENTERMONSTER); + else if (fastcmp(param, "monsterfloor") && fastcmp("true", val)) + sectors[i].activation = (sectors[i].activation | SECSPAC_FLOORMONSTER); + else if (fastcmp(param, "monsterceiling") && fastcmp("true", val)) + sectors[i].activation = (sectors[i].activation | SECSPAC_CEILINGMONSTER); + else if (fastcmp(param, "missileenter") && fastcmp("true", val)) + sectors[i].activation = (sectors[i].activation | SECSPAC_ENTERMISSILE); + else if (fastcmp(param, "missilefloor") && fastcmp("true", val)) + sectors[i].activation = (sectors[i].activation | SECSPAC_FLOORMISSILE); + else if (fastcmp(param, "missileceiling") && fastcmp("true", val)) + sectors[i].activation = (sectors[i].activation | SECSPAC_CEILINGMISSILE); } static void ParseTextmapSidedefParameter(UINT32 i, const char *param, const char *val) @@ -1968,7 +2031,7 @@ static void ParseTextmapLinedefParameter(UINT32 i, const char *param, const char else if (fastncmp(param, "stringarg", 9) && strlen(param) > 9) { size_t argnum = atol(param + 9); - if (argnum >= NUMLINESTRINGARGS) + if (argnum >= NUM_SCRIPT_STRINGARGS) return; lines[i].stringargs[argnum] = Z_Malloc(strlen(val) + 1, PU_LEVEL, NULL); M_Memcpy(lines[i].stringargs[argnum], val, strlen(val) + 1); @@ -1976,7 +2039,7 @@ static void ParseTextmapLinedefParameter(UINT32 i, const char *param, const char else if (fastncmp(param, "arg", 3) && strlen(param) > 3) { size_t argnum = atol(param + 3); - if (argnum >= NUMLINEARGS) + if (argnum >= NUM_SCRIPT_ARGS) return; lines[i].args[argnum] = atol(val); } @@ -2037,12 +2100,30 @@ static void ParseTextmapLinedefParameter(UINT32 i, const char *param, const char lines[i].flags |= ML_BOUNCY; else if (fastcmp(param, "transfer") && fastcmp("true", val)) lines[i].flags |= ML_TFERLINE; + // Activation flags + else if (fastcmp(param, "repeatspecial") && fastcmp("true", val)) + lines[i].activation |= SPAC_REPEATSPECIAL; + else if (fastcmp(param, "playercross") && fastcmp("true", val)) + lines[i].activation |= SPAC_CROSS; + else if (fastcmp(param, "monstercross") && fastcmp("true", val)) + lines[i].activation |= SPAC_CROSSMONSTER; + else if (fastcmp(param, "missilecross") && fastcmp("true", val)) + lines[i].activation |= SPAC_CROSSMISSILE; + else if (fastcmp(param, "playerpush") && fastcmp("true", val)) + lines[i].activation |= SPAC_PUSH; + else if (fastcmp(param, "monsterpush") && fastcmp("true", val)) + lines[i].activation |= SPAC_PUSHMONSTER; + else if (fastcmp(param, "impact") && fastcmp("true", val)) + lines[i].activation |= SPAC_IMPACT; } static void ParseTextmapThingParameter(UINT32 i, const char *param, const char *val) { if (fastcmp(param, "id")) + { + mapthings[i].tid = atol(val); Tag_FSet(&mapthings[i].tags, atol(val)); + } else if (fastcmp(param, "moreids")) { const char* id = val; @@ -2084,7 +2165,7 @@ static void ParseTextmapThingParameter(UINT32 i, const char *param, const char * else if (fastncmp(param, "stringarg", 9) && strlen(param) > 9) { size_t argnum = atol(param + 9); - if (argnum >= NUMMAPTHINGSTRINGARGS) + if (argnum >= NUM_MAPTHING_STRINGARGS) return; mapthings[i].stringargs[argnum] = Z_Malloc(strlen(val) + 1, PU_LEVEL, NULL); M_Memcpy(mapthings[i].stringargs[argnum], val, strlen(val) + 1); @@ -2092,10 +2173,47 @@ static void ParseTextmapThingParameter(UINT32 i, const char *param, const char * else if (fastncmp(param, "arg", 3) && strlen(param) > 3) { size_t argnum = atol(param + 3); - if (argnum >= NUMMAPTHINGARGS) + if (argnum >= NUM_MAPTHING_ARGS) + return; + mapthings[i].args[argnum] = atol(val); + } + + else if (fastcmp(param, "special")) + mapthings[i].special = atol(val); + else if (fastncmp(param, "scriptstringarg", 9) && strlen(param) > 9) + { + size_t argnum = atol(param + 9); + if (argnum >= NUM_SCRIPT_STRINGARGS) + return; + mapthings[i].script_stringargs[argnum] = Z_Malloc(strlen(val) + 1, PU_LEVEL, NULL); + M_Memcpy(mapthings[i].script_stringargs[argnum], val, strlen(val) + 1); + } + else if (fastncmp(param, "scriptarg", 3) && strlen(param) > 3) + { + size_t argnum = atol(param + 3); + if (argnum >= NUM_SCRIPT_ARGS) + return; + mapthings[i].script_args[argnum] = atol(val); + } +#if 0 + else if (fastncmp(param, "thingstringarg", 14) && strlen(param) > 14) + { + size_t argnum = atol(param + 14); + if (argnum >= NUM_MAPTHING_STRINGARGS) + return; + size_t len = strlen(val); + mapthings[i].stringargs[argnum] = Z_Malloc(len + 1, PU_LEVEL, NULL); + M_Memcpy(mapthings[i].stringargs[argnum], val, len); + mapthings[i].stringargs[argnum][len] = '\0'; + } + else if (fastncmp(param, "thingarg", 8) && strlen(param) > 8) + { + size_t argnum = atol(param + 8); + if (argnum >= NUM_MAPTHING_ARGS) return; mapthings[i].args[argnum] = atol(val); } +#endif } /** From a given position table, run a specified parser function through a {}-encapsuled text. @@ -2200,16 +2318,14 @@ typedef struct static void P_WriteTextmap_Things(FILE *f, const mapthing_t *wmapthings) { size_t i, j; - mtag_t firsttag; // Actual writing for (i = 0; i < nummapthings; i++) { fprintf(f, "thing // %s\n", sizeu1(i)); fprintf(f, "{\n"); - firsttag = Tag_FGet(&wmapthings[i].tags); - if (firsttag != 0) - fprintf(f, "id = %d;\n", firsttag); + if (wmapthings[i].tid != 0) + fprintf(f, "id = %d;\n", wmapthings[i].tid); if (wmapthings[i].tags.count > 1) { fprintf(f, "moreids = \""); @@ -2240,12 +2356,21 @@ static void P_WriteTextmap_Things(FILE *f, const mapthing_t *wmapthings) fprintf(f, "mobjscale = %f;\n", FIXED_TO_FLOAT(wmapthings[i].scale)); if (wmapthings[i].options & MTF_OBJECTFLIP) fprintf(f, "flip = true;\n"); - for (j = 0; j < NUMMAPTHINGARGS; j++) + if (wmapthings[i].special != 0) + fprintf(f, "special = %d;\n", wmapthings[i].special); + for (j = 0; j < NUM_SCRIPT_ARGS; j++) + if (wmapthings[i].script_args[j] != 0) + fprintf(f, "arg%s = %d;\n", sizeu1(j), wmapthings[i].script_args[j]); + for (j = 0; j < NUM_SCRIPT_STRINGARGS; j++) + if (mapthings[i].script_stringargs[j]) + fprintf(f, "stringarg%s = \"%s\";\n", sizeu1(j), mapthings[i].script_stringargs[j]); + for (j = 0; j < NUM_MAPTHING_ARGS; j++) if (wmapthings[i].args[j] != 0) - fprintf(f, "arg%s = %d;\n", sizeu1(j), wmapthings[i].args[j]); - for (j = 0; j < NUMMAPTHINGSTRINGARGS; j++) + fprintf(f, "thingarg%s = %d;\n", sizeu1(j), wmapthings[i].args[j]); + for (j = 0; j < NUM_MAPTHING_STRINGARGS; j++) if (mapthings[i].stringargs[j]) - fprintf(f, "stringarg%s = \"%s\";\n", sizeu1(j), mapthings[i].stringargs[j]); + fprintf(f, "thingstringarg%s = \"%s\";\n", sizeu1(j), mapthings[i].stringargs[j]); + fprintf(f, "}\n"); fprintf(f, "\n"); } @@ -2518,6 +2643,7 @@ static void P_WriteTextmap(void) } fprintf(f, "namespace = \"srb2\";\n"); + fprintf(f, "version = %d;\n", UDMF_CURRENT_VERSION); P_WriteTextmap_Things(f, wmapthings); for (i = 0; i < numvertexes; i++) @@ -2559,10 +2685,10 @@ static void P_WriteTextmap(void) } if (wlines[i].special != 0) fprintf(f, "special = %d;\n", wlines[i].special); - for (j = 0; j < NUMLINEARGS; j++) + for (j = 0; j < NUM_SCRIPT_ARGS; j++) if (wlines[i].args[j] != 0) fprintf(f, "arg%s = %d;\n", sizeu1(j), wlines[i].args[j]); - for (j = 0; j < NUMLINESTRINGARGS; j++) + for (j = 0; j < NUM_SCRIPT_STRINGARGS; j++) if (lines[i].stringargs[j]) fprintf(f, "stringarg%s = \"%s\";\n", sizeu1(j), lines[i].stringargs[j]); if (wlines[i].alpha != FRACUNIT) @@ -2625,6 +2751,20 @@ static void P_WriteTextmap(void) fprintf(f, "bouncy = true;\n"); if (wlines[i].flags & ML_TFERLINE) fprintf(f, "transfer = true;\n"); + if (wlines[i].activation & SPAC_REPEATSPECIAL) + fprintf(f, "repeatspecial = true;\n"); + if (wlines[i].activation & SPAC_CROSS) + fprintf(f, "playercross = true;\n"); + if (wlines[i].activation & SPAC_CROSSMONSTER) + fprintf(f, "monstercross = true;\n"); + if (wlines[i].activation & SPAC_CROSSMISSILE) + fprintf(f, "missilecross = true;\n"); + if (wlines[i].activation & SPAC_PUSH) + fprintf(f, "playerpush = true;\n"); + if (wlines[i].activation & SPAC_PUSHMONSTER) + fprintf(f, "monsterpush = true;\n"); + if (wlines[i].activation & SPAC_IMPACT) + fprintf(f, "impact = true;\n"); fprintf(f, "}\n"); fprintf(f, "\n"); } @@ -2879,6 +3019,45 @@ static void P_WriteTextmap(void) break; } } + if (wsectors[i].action != 0) + fprintf(f, "action = %d;\n", wsectors[i].action); + for (j = 0; j < NUM_SCRIPT_ARGS; j++) + if (wsectors[i].args[j] != 0) + fprintf(f, "arg%s = %d;\n", sizeu1(j), wsectors[i].args[j]); + for (j = 0; j < NUM_SCRIPT_STRINGARGS; j++) + if (wsectors[i].stringargs[j]) + fprintf(f, "stringarg%s = \"%s\";\n", sizeu1(j), wsectors[i].stringargs[j]); + switch (wsectors[i].activation & SECSPAC_TRIGGERMASK) + { + case SECSPAC_REPEATSPECIAL: + { + fprintf(f, "repeatspecial = true;\n"); + break; + } + case SECSPAC_CONTINUOUSSPECIAL: + { + fprintf(f, "continuousspecial = true;\n"); + break; + } + } + if (wsectors[i].activation & SECSPAC_ENTER) + fprintf(f, "playerenter = true;\n"); + if (wsectors[i].activation & SECSPAC_FLOOR) + fprintf(f, "playerfloor = true;\n"); + if (wsectors[i].activation & SECSPAC_CEILING) + fprintf(f, "playerceiling = true;\n"); + if (wsectors[i].activation & SECSPAC_ENTERMONSTER) + fprintf(f, "monsterenter = true;\n"); + if (wsectors[i].activation & SECSPAC_FLOORMONSTER) + fprintf(f, "monsterfloor = true;\n"); + if (wsectors[i].activation & SECSPAC_CEILINGMONSTER) + fprintf(f, "monsterceiling = true;\n"); + if (wsectors[i].activation & SECSPAC_ENTERMISSILE) + fprintf(f, "missileenter = true;\n"); + if (wsectors[i].activation & SECSPAC_FLOORMISSILE) + fprintf(f, "missilefloor = true;\n"); + if (wsectors[i].activation & SECSPAC_CEILINGMISSILE) + fprintf(f, "missileceiling = true;\n"); fprintf(f, "}\n"); fprintf(f, "\n"); } @@ -2985,6 +3164,11 @@ static void P_LoadTextmap(void) sc->friction = ORIG_FRICTION; + sc->action = 0; + memset(sc->args, 0, NUM_SCRIPT_ARGS*sizeof(*sc->args)); + memset(sc->stringargs, 0x00, NUM_SCRIPT_STRINGARGS*sizeof(*sc->stringargs)); + sc->activation = 0; + textmap_colormap.used = false; textmap_colormap.lightcolor = 0; textmap_colormap.lightalpha = 25; @@ -3039,12 +3223,13 @@ static void P_LoadTextmap(void) ld->special = 0; Tag_FSet(&ld->tags, 0); - memset(ld->args, 0, NUMLINEARGS*sizeof(*ld->args)); - memset(ld->stringargs, 0x00, NUMLINESTRINGARGS*sizeof(*ld->stringargs)); + memset(ld->args, 0, NUM_SCRIPT_ARGS*sizeof(*ld->args)); + memset(ld->stringargs, 0x00, NUM_SCRIPT_STRINGARGS*sizeof(*ld->stringargs)); ld->alpha = FRACUNIT; ld->executordelay = 0; ld->sidenum[0] = NO_SIDEDEF; ld->sidenum[1] = NO_SIDEDEF; + ld->activation = 0; TextmapParse(linesPos[i], i, ParseTextmapLinedefParameter); @@ -3090,11 +3275,15 @@ static void P_LoadTextmap(void) mt->options = 0; mt->z = 0; mt->extrainfo = 0; + mt->tid = 0; Tag_FSet(&mt->tags, 0); mt->scale = FRACUNIT; mt->spritexscale = mt->spriteyscale = FRACUNIT; - memset(mt->args, 0, NUMMAPTHINGARGS*sizeof(*mt->args)); - memset(mt->stringargs, 0x00, NUMMAPTHINGSTRINGARGS*sizeof(*mt->stringargs)); + memset(mt->args, 0, NUM_MAPTHING_ARGS*sizeof(*mt->args)); + memset(mt->stringargs, 0x00, NUM_MAPTHING_STRINGARGS*sizeof(*mt->stringargs)); + mt->special = 0; + memset(mt->script_args, 0, NUM_SCRIPT_ARGS*sizeof(*mt->script_args)); + memset(mt->script_stringargs, 0x00, NUM_SCRIPT_STRINGARGS*sizeof(*mt->script_stringargs)); mt->mobj = NULL; TextmapParse(mapthingsPos[i], i, ParseTextmapThingParameter); @@ -7048,7 +7237,9 @@ static boolean P_LoadMapFromFile(void) virtres_t *virt = vres_GetMap(lastloadedmaplumpnum); virtlump_t *textmap = vres_Find(virt, "TEXTMAP"); size_t i; + udmf = textmap != NULL; + udmf_version = 0; if (!P_LoadMapData(virt)) return false; @@ -7887,6 +8078,8 @@ boolean P_LoadLevel(boolean fromnetsave, boolean reloadinggamestate) // Close text prompt before freeing the old level F_EndTextPrompt(false, true); + ACS_InvalidateMapScope(); + LUA_InvalidateLevel(); for (ss = sectors; sectors+numsectors != ss; ss++) @@ -7995,8 +8188,13 @@ boolean P_LoadLevel(boolean fromnetsave, boolean reloadinggamestate) // a netgame save is being loaded, and could actively be harmful by messing with // the client's view of the data.) if (!fromnetsave) + { P_InitGametype(); + // Initialize ACS scripts + ACS_LoadLevelScripts(gamemap); + } + if (!reloadinggamestate) { P_InitCamera(); @@ -8021,8 +8219,6 @@ boolean P_LoadLevel(boolean fromnetsave, boolean reloadinggamestate) P_RunCachedActions(); - P_MapEnd(); // tmthing is no longer needed from this point onwards - // Took me 3 hours to figure out why my progression kept on getting overwritten with the titlemap... if (!titlemapinaction) { @@ -8039,6 +8235,20 @@ boolean P_LoadLevel(boolean fromnetsave, boolean reloadinggamestate) lastmaploaded = gamemap; // HAS to be set after saving!! } + ACS_RunLevelStartScripts(); + + for (i = 0; i < MAXPLAYERS; i++) + { + if (!playeringame[i]) + continue; + if (players[i].spectator) + continue; + ACS_RunPlayerEnterScript(&players[i]); + players[i].enteredgame = true; + } + + P_MapEnd(); // tmthing is no longer needed from this point onwards + if (!fromnetsave) // uglier hack { // to make a newly loaded level start on the second frame. INT32 buf = gametic % BACKUPTICS; diff --git a/src/p_setup.h b/src/p_setup.h index da38d4c08b152f87fab59517706c3ccbc7544339..b797920c0801e0b0193b24b878ee79738adc615c 100644 --- a/src/p_setup.h +++ b/src/p_setup.h @@ -2,7 +2,7 @@ //----------------------------------------------------------------------------- // Copyright (C) 1993-1996 by id Software, Inc. // Copyright (C) 1998-2000 by DooM Legacy Team. -// Copyright (C) 1999-2023 by Sonic Team Junior. +// Copyright (C) 1999-2024 by Sonic Team Junior. // // This program is free software distributed under the // terms of the GNU General Public License, version 2. @@ -18,6 +18,10 @@ #include "doomstat.h" #include "r_defs.h" +#ifdef __cplusplus +extern "C" { +#endif + // map md5, sent to players via PT_SERVERINFO extern unsigned char mapmd5[16]; @@ -89,4 +93,8 @@ UINT32 P_GetScoreForGrade(INT16 map, UINT8 mare, UINT8 grade); UINT32 P_GetScoreForGradeOverall(INT16 map, UINT8 grade); void P_AddNiGHTSTimes(INT16 i, char *gtext); +#ifdef __cplusplus +} // extern "C" +#endif + #endif diff --git a/src/p_spec.c b/src/p_spec.c index 805817fb033c465b33059c24fbefb52432173444..6dc5e0ee8aefb85423898fe125f95ad895c7c0f8 100644 --- a/src/p_spec.c +++ b/src/p_spec.c @@ -2,7 +2,7 @@ //----------------------------------------------------------------------------- // Copyright (C) 1993-1996 by id Software, Inc. // Copyright (C) 1998-2000 by DooM Legacy Team. -// Copyright (C) 1999-2023 by Sonic Team Junior. +// Copyright (C) 1999-2024 by Sonic Team Junior. // // This program is free software distributed under the // terms of the GNU General Public License, version 2. @@ -38,6 +38,7 @@ #include "m_misc.h" #include "m_cond.h" //unlock triggers #include "lua_hook.h" // LUA_HookLinedefExecute +#include "acs/interface.h" #include "f_finale.h" // control text prompt #include "r_skins.h" // skins @@ -1565,6 +1566,11 @@ static boolean P_CheckEmeralds(INT32 checktype, UINT16 target) } } +boolean P_CanActivateSpecial(INT16 special) +{ + return (special >= 400 && special < 500); +} + static void P_ActivateLinedefExecutor(line_t *line, mobj_t *actor, sector_t *caller) { if (line->special < 400 || line->special >= 500) @@ -2155,6 +2161,226 @@ void P_SwitchWeather(INT32 weathernum) } } +static void P_LineSpecialWasActivated(line_t *line) +{ + if (!(line->activation & SPAC_REPEATSPECIAL)) + { + line->special = 0; + } +} + +static boolean P_AllowSpecialCross(line_t *line, mobj_t *thing) +{ + if (P_CanActivateSpecial(line->special) == false) + { + // No special to even activate. + return false; + } + + if (thing->player != NULL) + { + return !!(line->activation & SPAC_CROSS); + } + else if ((thing->flags & (MF_ENEMY|MF_BOSS)) != 0) + { + return !!(line->activation & SPAC_CROSSMONSTER); + } + else if (thing->flags & MF_MISSILE) + { + return !!(line->activation & SPAC_CROSSMISSILE); + } + + // No activation flags for you. + return false; +} + +// +// P_CrossSpecialLine - TRIGGER +// Called every time a thing origin is about +// to cross a line with specific specials +// +void P_CrossSpecialLine(line_t *line, INT32 side, mobj_t *thing) +{ + player_t *player = NULL; + activator_t *activator = NULL; + boolean result = false; + + if (thing == NULL || P_MobjWasRemoved(thing) == true || thing->health <= 0) + { + // Invalid mobj. + return; + } + + player = thing->player; + + if (player != NULL) + { + if (player->spectator == true) + { + // Ignore spectators. + return; + } + } + + if (P_AllowSpecialCross(line, thing) == false) + { + // This special can't be activated this way. + return; + } + + activator = Z_Calloc(sizeof(activator_t), PU_LEVEL, NULL); + I_Assert(activator != NULL); + + P_SetTarget(&activator->mo, thing); + activator->line = line; + activator->side = side; + activator->sector = (side != 0) ? line->backsector : line->frontsector; + + result = P_ProcessSpecial(activator, line->special, line->args, line->stringargs); + + P_SetTarget(&activator->mo, NULL); + Z_Free(activator); + + if (result == true) + { + P_LineSpecialWasActivated(line); + } +} + +static boolean P_AllowSpecialPush(line_t *line, mobj_t *thing) +{ + if (P_CanActivateSpecial(line->special) == false) + { + // No special to even activate. + return false; + } + + if (thing->player != NULL) + { + return !!(line->activation & SPAC_PUSH); + } + else if ((thing->flags & (MF_ENEMY|MF_BOSS)) != 0) + { + return !!(line->activation & SPAC_PUSHMONSTER); + } + else if (thing->flags & MF_MISSILE) + { + return !!(line->activation & SPAC_IMPACT); + } + + // No activation flags for you. + return false; +} + +// +// P_PushSpecialLine - TRIGGER +// Called every time a thing origin is blocked +// by a line with specific specials +// +void P_PushSpecialLine(line_t *line, mobj_t *thing) +{ + player_t *player = NULL; + activator_t *activator = NULL; + boolean result = false; + + if (thing == NULL || P_MobjWasRemoved(thing) == true || thing->health <= 0) + { + // Invalid mobj. + return; + } + + if (line == NULL) + { + // Invalid line. + return; + } + + player = thing->player; + + if (player != NULL) + { + if (player->spectator == true) + { + // Ignore spectators. + return; + } + } + + if (P_AllowSpecialPush(line, thing) == false) + { + // This special can't be activated this way. + return; + } + + activator = Z_Calloc(sizeof(activator_t), PU_LEVEL, NULL); + I_Assert(activator != NULL); + + P_SetTarget(&activator->mo, thing); + activator->line = line; + activator->side = P_PointOnLineSide(thing->x, thing->y, line); + activator->sector = (activator->side != 0) ? line->backsector : line->frontsector; + + result = P_ProcessSpecial(activator, line->special, line->args, line->stringargs); + + P_SetTarget(&activator->mo, NULL); + Z_Free(activator); + + if (result == true) + { + P_LineSpecialWasActivated(line); + } +} + +// +// P_ActivateThingSpecial - TRIGGER +// Called when a thing is killed, or upon +// any other type-specific conditions +// +void P_ActivateThingSpecial(mobj_t *mo, mobj_t *source) +{ + player_t *player = NULL; + activator_t *activator = NULL; + + if (mo == NULL || P_MobjWasRemoved(mo) == true) + { + // Invalid mobj. + return; + } + + // Is this necessary? Probably not, but I hate + // spectators so I will manually ensure they + // can't impact the gamestate anyway. + player = mo->player; + if (player != NULL) + { + if (player->spectator == true) + { + // Ignore spectators. + return; + } + } + + if (P_CanActivateSpecial(mo->special) == false) + { + // No special to even activate. + return; + } + + activator = Z_Calloc(sizeof(activator_t), PU_LEVEL, NULL); + I_Assert(activator != NULL); + + if (source != NULL) + { + P_SetTarget(&activator->mo, source); + activator->sector = source->subsector->sector; + } + + P_ProcessSpecial(activator, mo->special, mo->script_args, mo->script_stringargs); + + P_SetTarget(&activator->mo, NULL); + Z_Free(activator); +} + /** Gets an object. * * \param type Object type to look for. @@ -2176,7 +2402,7 @@ static mobj_t *P_GetObjectTypeInSectorNum(mobjtype_t type, size_t s) return NULL; } -static mobj_t* P_FindObjectTypeFromTag(mobjtype_t type, mtag_t tag) +mobj_t* P_FindObjectTypeFromTag(mobjtype_t type, mtag_t tag) { if (udmf) { @@ -2225,6 +2451,35 @@ static mobj_t* P_FindObjectTypeFromTag(mobjtype_t type, mtag_t tag) */ static void P_ProcessLineSpecial(line_t *line, mobj_t *mo, sector_t *callsec) { + activator_t *activator = NULL; + + if (line == NULL) + { + // No line to activate + return; + } + + activator = Z_Calloc(sizeof(activator_t), PU_LEVEL, NULL); + I_Assert(activator != NULL); + + P_SetTarget(&activator->mo, mo); + activator->line = line; + activator->sector = callsec; + + P_ProcessSpecial(activator, line->special, line->args, line->stringargs); + + P_SetTarget(&activator->mo, NULL); + Z_Free(activator); + + // Intentionally no P_LineSpecialWasActivated call. +} + +boolean P_ProcessSpecial(activator_t *activator, INT16 special, INT32 *args, char **stringargs) +{ + line_t *line = activator->line; // If called from a linedef executor, this is the control sector linedef. If from a script, then it's the actual activator. + mobj_t *mo = activator->mo; + sector_t *callsec = activator->sector; + INT32 secnum = -1; mobj_t *bot = NULL; @@ -2234,13 +2489,13 @@ static void P_ProcessLineSpecial(line_t *line, mobj_t *mo, sector_t *callsec) bot = players[secondarydisplayplayer].mo; // note: only commands with linedef types >= 400 && < 500 can be used - switch (line->special) + switch (special) { case 400: // Set tagged sector's heights/flats - if (line->args[1] != TMP_CEILING) - EV_DoFloor(line->args[0], line, instantMoveFloorByFrontSector); - if (line->args[1] != TMP_FLOOR) - EV_DoCeiling(line->args[0], line, instantMoveCeilingByFrontSector); + if (args[1] != TMP_CEILING) + EV_DoFloor(args[0], line, instantMoveFloorByFrontSector); + if (args[1] != TMP_FLOOR) + EV_DoCeiling(args[0], line, instantMoveCeilingByFrontSector); break; case 402: // Copy light level to tagged sectors @@ -2258,7 +2513,7 @@ static void P_ProcessLineSpecial(line_t *line, mobj_t *mo, sector_t *callsec) newfloorlightsec = line->frontsector->floorlightsec; newceilinglightsec = line->frontsector->ceilinglightsec; - TAG_ITER_SECTORS(line->args[0], secnum) + TAG_ITER_SECTORS(args[0], secnum) { if (sectors[secnum].lightingdata) { @@ -2267,15 +2522,15 @@ static void P_ProcessLineSpecial(line_t *line, mobj_t *mo, sector_t *callsec) sectors[secnum].lightingdata = NULL; } - if (!(line->args[1] & TMLC_NOSECTOR)) + if (!(args[1] & TMLC_NOSECTOR)) sectors[secnum].lightlevel = newlightlevel; - if (!(line->args[1] & TMLC_NOFLOOR)) + if (!(args[1] & TMLC_NOFLOOR)) { sectors[secnum].floorlightlevel = newfloorlightlevel; sectors[secnum].floorlightabsolute = newfloorlightabsolute; sectors[secnum].floorlightsec = newfloorlightsec; } - if (!(line->args[1] & TMLC_NOCEILING)) + if (!(args[1] & TMLC_NOCEILING)) { sectors[secnum].ceilinglightlevel = newceilinglightlevel; sectors[secnum].ceilinglightabsolute = newceilinglightabsolute; @@ -2286,26 +2541,26 @@ static void P_ProcessLineSpecial(line_t *line, mobj_t *mo, sector_t *callsec) break; case 403: // Move planes by front sector - if (line->args[1] != TMP_CEILING) - EV_DoFloor(line->args[0], line, moveFloorByFrontSector); - if (line->args[1] != TMP_FLOOR) - EV_DoCeiling(line->args[0], line, moveCeilingByFrontSector); + if (args[1] != TMP_CEILING) + EV_DoFloor(args[0], line, moveFloorByFrontSector); + if (args[1] != TMP_FLOOR) + EV_DoCeiling(args[0], line, moveCeilingByFrontSector); break; case 405: // Move planes by distance - if (line->args[1] != TMP_CEILING) - EV_DoFloor(line->args[0], line, moveFloorByDistance); - if (line->args[1] != TMP_FLOOR) - EV_DoCeiling(line->args[0], line, moveCeilingByDistance); + if (args[1] != TMP_CEILING) + EV_DoFloor(args[0], line, moveFloorByDistance); + if (args[1] != TMP_FLOOR) + EV_DoCeiling(args[0], line, moveCeilingByDistance); break; case 408: // Set flats { - TAG_ITER_SECTORS(line->args[0], secnum) + TAG_ITER_SECTORS(args[0], secnum) { - if (line->args[1] != TMP_CEILING) + if (args[1] != TMP_CEILING) sectors[secnum].floorpic = line->frontsector->floorpic; - if (line->args[1] != TMP_FLOOR) + if (args[1] != TMP_FLOOR) sectors[secnum].ceilingpic = line->frontsector->ceilingpic; } break; @@ -2314,11 +2569,11 @@ static void P_ProcessLineSpecial(line_t *line, mobj_t *mo, sector_t *callsec) case 409: // Change tagged sectors' tag // (formerly "Change calling sectors' tag", but behavior was changed) { - mtag_t newtag = line->args[1]; + mtag_t newtag = args[1]; - TAG_ITER_SECTORS(line->args[0], secnum) + TAG_ITER_SECTORS(args[0], secnum) { - switch (line->args[2]) + switch (args[2]) { case TMT_ADD: Tag_SectorAdd(secnum, newtag); @@ -2340,10 +2595,10 @@ static void P_ProcessLineSpecial(line_t *line, mobj_t *mo, sector_t *callsec) case 410: // Change front sector's tag { - mtag_t newtag = line->args[1]; + mtag_t newtag = args[1]; secnum = (UINT32)(line->frontsector - sectors); - switch (line->args[2]) + switch (args[2]) { case TMT_ADD: Tag_SectorAdd(secnum, newtag); @@ -2363,7 +2618,7 @@ static void P_ProcessLineSpecial(line_t *line, mobj_t *mo, sector_t *callsec) } case 411: // Stop floor/ceiling movement in tagged sector(s) - TAG_ITER_SECTORS(line->args[0], secnum) + TAG_ITER_SECTORS(args[0], secnum) { if (sectors[secnum].floordata) { @@ -2395,15 +2650,15 @@ static void P_ProcessLineSpecial(line_t *line, mobj_t *mo, sector_t *callsec) mobj_t *dest; if (!mo) // nothing to teleport - return; + return false; - if (line->args[1] & TMT_RELATIVE) // Relative silent teleport + if (args[1] & TMT_RELATIVE) // Relative silent teleport { fixed_t x, y, z; - x = line->args[2] << FRACBITS; - y = line->args[3] << FRACBITS; - z = line->args[4] << FRACBITS; + x = args[2] << FRACBITS; + y = args[3] << FRACBITS; + z = args[4] << FRACBITS; P_UnsetThingPosition(mo); mo->x += x; @@ -2436,13 +2691,13 @@ static void P_ProcessLineSpecial(line_t *line, mobj_t *mo, sector_t *callsec) angle_t angle; boolean silent, keepmomentum; - dest = P_FindObjectTypeFromTag(MT_TELEPORTMAN, line->args[0]); + dest = P_FindObjectTypeFromTag(MT_TELEPORTMAN, args[0]); if (!dest) - return; + return false; - angle = (line->args[1] & TMT_KEEPANGLE) ? mo->angle : dest->angle; - silent = !!(line->args[1] & TMT_SILENT); - keepmomentum = !!(line->args[1] & TMT_KEEPMOMENTUM); + angle = (args[1] & TMT_KEEPANGLE) ? mo->angle : dest->angle; + silent = !!(args[1] & TMT_SILENT); + keepmomentum = !!(args[1] & TMT_KEEPMOMENTUM); if (bot) P_Teleport(bot, dest->x, dest->y, dest->z, angle, !silent, keepmomentum); @@ -2455,18 +2710,18 @@ static void P_ProcessLineSpecial(line_t *line, mobj_t *mo, sector_t *callsec) case 413: // Change music // console player only unless TMM_ALLPLAYERS is set - if ((line->args[0] & TMM_ALLPLAYERS) || (mo && mo->player && P_IsLocalPlayer(mo->player)) || titlemapinaction) + if ((args[0] & TMM_ALLPLAYERS) || (mo && mo->player && P_IsLocalPlayer(mo->player)) || titlemapinaction) { - boolean musicsame = (!line->stringargs[0] || !line->stringargs[0][0] || !strnicmp(line->stringargs[0], S_MusicName(), 7)); - UINT16 tracknum = (UINT16)max(line->args[6], 0); - INT32 position = (INT32)max(line->args[1], 0); - UINT32 prefadems = (UINT32)max(line->args[2], 0); - UINT32 postfadems = (UINT32)max(line->args[3], 0); - UINT8 fadetarget = (UINT8)max(line->args[4], 0); - INT16 fadesource = (INT16)max(line->args[5], -1); + boolean musicsame = (!stringargs[0] || !stringargs[0][0] || !strnicmp(stringargs[0], S_MusicName(), 7)); + UINT16 tracknum = (UINT16)max(args[6], 0); + INT32 position = (INT32)max(args[1], 0); + UINT32 prefadems = (UINT32)max(args[2], 0); + UINT32 postfadems = (UINT32)max(args[3], 0); + UINT8 fadetarget = (UINT8)max(args[4], 0); + INT16 fadesource = (INT16)max(args[5], -1); // Seek offset from current song position - if (line->args[0] & TMM_OFFSET) + if (args[0] & TMM_OFFSET) { // adjust for loop point if subtracting if (position < 0 && S_GetMusicLength() && @@ -2478,7 +2733,7 @@ static void P_ProcessLineSpecial(line_t *line, mobj_t *mo, sector_t *callsec) } // Fade current music to target volume (if music won't be changed) - if ((line->args[0] & TMM_FADE) && fadetarget && musicsame) + if ((args[0] & TMM_FADE) && fadetarget && musicsame) { // 0 fadesource means fade from current volume. // meaning that we can't specify volume 0 as the source volume -- this starts at 1. @@ -2496,27 +2751,27 @@ static void P_ProcessLineSpecial(line_t *line, mobj_t *mo, sector_t *callsec) // Change the music and apply position/fade operations else { - if (!line->stringargs[0] || !strcmp(line->stringargs[0], "-")) + if (!stringargs[0] || !strcmp(stringargs[0], "-")) strcpy(mapmusname, ""); else { - strncpy(mapmusname, line->stringargs[0], 7); + strncpy(mapmusname, stringargs[0], 7); mapmusname[6] = 0; } mapmusflags = tracknum & MUSIC_TRACKMASK; - if (!(line->args[0] & TMM_NORELOAD)) + if (!(args[0] & TMM_NORELOAD)) mapmusflags |= MUSIC_RELOADRESET; - if (line->args[0] & TMM_FORCERESET) + if (args[0] & TMM_FORCERESET) mapmusflags |= MUSIC_FORCERESET; mapmusposition = position; - S_ChangeMusicEx(mapmusname, mapmusflags, !(line->args[0] & TMM_NOLOOP), position, - !(line->args[0] & TMM_FADE) ? prefadems : 0, - !(line->args[0] & TMM_FADE) ? postfadems : 0); + S_ChangeMusicEx(mapmusname, mapmusflags, !(args[0] & TMM_NOLOOP), position, + !(args[0] & TMM_FADE) ? prefadems : 0, + !(args[0] & TMM_FADE) ? postfadems : 0); - if ((line->args[0] & TMM_FADE) && fadetarget) + if ((args[0] & TMM_FADE) && fadetarget) { if (!postfadems) S_SetInternalMusicVolume(fadetarget); @@ -2531,16 +2786,16 @@ static void P_ProcessLineSpecial(line_t *line, mobj_t *mo, sector_t *callsec) break; case 414: // Play SFX - P_PlaySFX(line->stringargs[0] ? get_number(line->stringargs[0]) : sfx_None, mo, callsec, line->args[2], line->args[0], line->args[1]); + P_PlaySFX(stringargs[0] ? get_number(stringargs[0]) : sfx_None, mo, callsec, args[2], args[0], args[1]); break; case 415: // Run a script if (cv_runscripts.value) { - lumpnum_t lumpnum = W_CheckNumForName(line->stringargs[0]); + lumpnum_t lumpnum = W_CheckNumForName(stringargs[0]); if (lumpnum == LUMPERROR || W_LumpLength(lumpnum) == 0) - CONS_Debug(DBG_SETUP, "Line type 415 Executor: script lump %s not found/not valid.\n", line->stringargs[0]); + CONS_Debug(DBG_SETUP, "Line type 415 Executor: script lump %s not found/not valid.\n", stringargs[0]); else { void *lump = W_CacheLumpNum(lumpnum, PU_CACHE); @@ -2555,30 +2810,30 @@ static void P_ProcessLineSpecial(line_t *line, mobj_t *mo, sector_t *callsec) break; case 416: // Spawn adjustable fire flicker - TAG_ITER_SECTORS(line->args[0], secnum) - P_SpawnAdjustableFireFlicker(§ors[secnum], line->args[2], - line->args[3] ? sectors[secnum].lightlevel : line->args[4], line->args[1]); + TAG_ITER_SECTORS(args[0], secnum) + P_SpawnAdjustableFireFlicker(§ors[secnum], args[2], + args[3] ? sectors[secnum].lightlevel : args[4], args[1]); break; case 417: // Spawn adjustable glowing light - TAG_ITER_SECTORS(line->args[0], secnum) - P_SpawnAdjustableGlowingLight(§ors[secnum], line->args[2], - line->args[3] ? sectors[secnum].lightlevel : line->args[4], line->args[1]); + TAG_ITER_SECTORS(args[0], secnum) + P_SpawnAdjustableGlowingLight(§ors[secnum], args[2], + args[3] ? sectors[secnum].lightlevel : args[4], args[1]); break; case 418: // Spawn adjustable strobe flash - TAG_ITER_SECTORS(line->args[0], secnum) - P_SpawnAdjustableStrobeFlash(§ors[secnum], line->args[3], - (line->args[4] & TMB_USETARGET) ? sectors[secnum].lightlevel : line->args[5], - line->args[1], line->args[2], line->args[4] & TMB_SYNC); + TAG_ITER_SECTORS(args[0], secnum) + P_SpawnAdjustableStrobeFlash(§ors[secnum], args[3], + (args[4] & TMB_USETARGET) ? sectors[secnum].lightlevel : args[5], + args[1], args[2], args[4] & TMB_SYNC); break; case 420: // Fade light levels in tagged sectors to new value - P_FadeLight(line->args[0], line->args[1], line->args[2], line->args[3] & TMF_TICBASED, line->args[3] & TMF_OVERRIDE, line->args[3] & TMF_RELATIVE); + P_FadeLight(args[0], args[1], args[2], args[3] & TMF_TICBASED, args[3] & TMF_OVERRIDE, args[3] & TMF_RELATIVE); break; case 421: // Stop lighting effect in tagged sectors - TAG_ITER_SECTORS(line->args[0], secnum) + TAG_ITER_SECTORS(args[0], secnum) if (sectors[secnum].lightingdata) { P_RemoveThinker(&((thinkerdata_t *)sectors[secnum].lightingdata)->thinker); @@ -2592,11 +2847,11 @@ static void P_ProcessLineSpecial(line_t *line, mobj_t *mo, sector_t *callsec) INT32 aim; if ((!mo || !mo->player) && !titlemapinaction) // only players have views, and title screens - return; + return false; - altview = P_FindObjectTypeFromTag(MT_ALTVIEWMAN, line->args[0]); + altview = P_FindObjectTypeFromTag(MT_ALTVIEWMAN, args[0]); if (!altview || !altview->spawnpoint) - return; + return false; // If titlemap, set the camera ref for title's thinker // This is not revoked until overwritten; awayviewtics is ignored @@ -2611,7 +2866,7 @@ static void P_ProcessLineSpecial(line_t *line, mobj_t *mo, sector_t *callsec) P_ResetCamera(mo->player, &camera2); // reset p2 camera on p2 getting an awayviewmobj } - aim = udmf ? altview->spawnpoint->pitch : line->args[2]; + aim = udmf ? altview->spawnpoint->pitch : args[2]; aim = (aim + 360) % 360; aim *= (ANGLE_90>>8); aim /= 90; @@ -2620,30 +2875,30 @@ static void P_ProcessLineSpecial(line_t *line, mobj_t *mo, sector_t *callsec) titlemapcameraref->cusval = (angle_t)aim; else { mo->player->awayviewaiming = (angle_t)aim; - mo->player->awayviewtics = line->args[1]; + mo->player->awayviewtics = args[1]; } } break; case 423: // Change Sky - if ((mo && mo->player && P_IsLocalPlayer(mo->player)) || line->args[1]) - P_SetupLevelSky(line->args[0], line->args[1]); + if ((mo && mo->player && P_IsLocalPlayer(mo->player)) || args[1]) + P_SetupLevelSky(args[0], args[1]); break; case 424: // Change Weather - if (line->args[1]) + if (args[1]) { - globalweather = (UINT8)(line->args[0]); + globalweather = (UINT8)(args[0]); P_SwitchWeather(globalweather); } else if (mo && mo->player && P_IsLocalPlayer(mo->player)) - P_SwitchWeather(line->args[0]); + P_SwitchWeather(args[0]); break; case 425: // Calls P_SetMobjState on calling mobj if (mo && !mo->player) { - statenum_t state = line->stringargs[0] ? get_number(line->stringargs[0]) : S_NULL; + statenum_t state = stringargs[0] ? get_number(stringargs[0]) : S_NULL; if (state >= 0 && state < NUMSTATES) P_SetMobjState(mo, state); } @@ -2651,9 +2906,9 @@ static void P_ProcessLineSpecial(line_t *line, mobj_t *mo, sector_t *callsec) case 426: // Moves the mobj to its sector's soundorg and on the floor, and stops it if (!mo) - return; + return false; - if (line->args[0]) + if (args[0]) { P_UnsetThingPosition(mo); mo->x = mo->subsector->sector->soundorg.x; @@ -2674,7 +2929,7 @@ static void P_ProcessLineSpecial(line_t *line, mobj_t *mo, sector_t *callsec) // Reset bot too. if (bot) { - if (line->args[0]) + if (args[0]) P_SetOrigin(bot, mo->x, mo->y, mo->z); bot->momx = bot->momy = bot->momz = 1; bot->pmomz = 0; @@ -2688,26 +2943,26 @@ static void P_ProcessLineSpecial(line_t *line, mobj_t *mo, sector_t *callsec) case 427: // Awards points if the mobj is a player if (mo && mo->player) - P_AddPlayerScore(mo->player, line->args[0]); + P_AddPlayerScore(mo->player, args[0]); break; case 428: // Start floating platform movement - EV_DoElevator(line->args[0], line, elevateContinuous); + EV_DoElevator(args[0], line, elevateContinuous); break; case 429: // Crush planes once - if (line->args[1] == TMP_FLOOR) - EV_DoFloor(line->args[0], line, crushFloorOnce); - else if (line->args[1] == TMP_CEILING) - EV_DoCrush(line->args[0], line, crushCeilOnce); + if (args[1] == TMP_FLOOR) + EV_DoFloor(args[0], line, crushFloorOnce); + else if (args[1] == TMP_CEILING) + EV_DoCrush(args[0], line, crushCeilOnce); else - EV_DoCrush(line->args[0], line, crushBothOnce); + EV_DoCrush(args[0], line, crushBothOnce); break; case 432: // Enable/Disable 2D Mode if (mo && mo->player) { - if (line->args[0]) + if (args[0]) mo->flags2 &= ~MF2_TWOD; else mo->flags2 |= MF2_TWOD; @@ -2722,13 +2977,13 @@ static void P_ProcessLineSpecial(line_t *line, mobj_t *mo, sector_t *callsec) break; case 433: // Flip/flop gravity. Works on pushables, too! - if (line->args[1]) + if (args[1]) mo->flags2 ^= MF2_OBJECTFLIP; - else if (line->args[0]) + else if (args[0]) mo->flags2 &= ~MF2_OBJECTFLIP; else mo->flags2 |= MF2_OBJECTFLIP; - if (line->args[2]) + if (args[2]) mo->eflags |= MFE_VERTICALFLIP; if (bot) bot->flags2 = (bot->flags2 & ~MF2_OBJECTFLIP) | (mo->flags2 & MF2_OBJECTFLIP); @@ -2737,8 +2992,8 @@ static void P_ProcessLineSpecial(line_t *line, mobj_t *mo, sector_t *callsec) case 434: // Custom Power if (mo && mo->player) { - powertype_t power = line->stringargs[0] ? get_number(line->stringargs[0]) : 0; - INT32 value = line->stringargs[1] ? get_number(line->stringargs[1]) : 0; + powertype_t power = stringargs[0] ? get_number(stringargs[0]) : 0; + INT32 value = stringargs[1] ? get_number(stringargs[1]) : 0; if (value == -1) // 'Infinite' value = UINT16_MAX; @@ -2755,7 +3010,7 @@ static void P_ProcessLineSpecial(line_t *line, mobj_t *mo, sector_t *callsec) thinker_t *th; fixed_t length = R_PointToDist2(line->v2->x, line->v2->y, line->v1->x, line->v1->y); - fixed_t speed = line->args[1] << FRACBITS; + fixed_t speed = args[1] << FRACBITS; fixed_t dx = FixedMul(FixedMul(FixedDiv(line->dx, length), speed) >> SCROLL_SHIFT, CARRYFACTOR); fixed_t dy = FixedMul(FixedMul(FixedDiv(line->dy, length), speed) >> SCROLL_SHIFT, CARRYFACTOR); @@ -2765,7 +3020,7 @@ static void P_ProcessLineSpecial(line_t *line, mobj_t *mo, sector_t *callsec) continue; scroller = (scroll_t *)th; - if (!Tag_Find(§ors[scroller->affectee].tags, line->args[0])) + if (!Tag_Find(§ors[scroller->affectee].tags, args[0])) continue; scroller->dx = dx; @@ -2776,8 +3031,8 @@ static void P_ProcessLineSpecial(line_t *line, mobj_t *mo, sector_t *callsec) case 436: // Shatter block remotely { - INT16 sectag = (INT16)(line->args[0]); - INT16 foftag = (INT16)(line->args[1]); + INT16 sectag = (INT16)(args[0]); + INT16 foftag = (INT16)(args[1]); sector_t *sec; // Sector that the FOF is visible in ffloor_t *rover; // FOF that we are going to crumble boolean foundrover = false; // for debug, "Can't find a FOF" message @@ -2789,7 +3044,7 @@ static void P_ProcessLineSpecial(line_t *line, mobj_t *mo, sector_t *callsec) if (!sec->ffloors) { CONS_Debug(DBG_GAMELOGIC, "Line type 436 Executor: Target sector #%d has no FOFs.\n", secnum); - return; + return false; } for (rover = sec->ffloors; rover; rover = rover->next) @@ -2805,7 +3060,7 @@ static void P_ProcessLineSpecial(line_t *line, mobj_t *mo, sector_t *callsec) if (!foundrover) { CONS_Debug(DBG_GAMELOGIC, "Line type 436 Executor: Can't find a FOF control sector with tag %d\n", foftag); - return; + return false; } } } @@ -2814,10 +3069,10 @@ static void P_ProcessLineSpecial(line_t *line, mobj_t *mo, sector_t *callsec) case 437: // Disable Player Controls if (mo && mo->player) { - UINT16 fractime = (UINT16)(line->args[0]); + UINT16 fractime = (UINT16)(args[0]); if (fractime < 1) fractime = 1; //instantly wears off upon leaving - if (line->args[1]) + if (args[1]) fractime |= 1<<15; //more crazy &ing, as if music stuff wasn't enough mo->player->powers[pw_nocontrol] = fractime; if (bot) @@ -2828,7 +3083,7 @@ static void P_ProcessLineSpecial(line_t *line, mobj_t *mo, sector_t *callsec) case 438: // Set player scale if (mo) { - mo->destscale = FixedDiv(line->args[0]<<FRACBITS, 100<<FRACBITS); + mo->destscale = FixedDiv(args[0]<<FRACBITS, 100<<FRACBITS); if (mo->destscale < FRACUNIT/100) mo->destscale = FRACUNIT/100; if (mo->player && bot) @@ -2840,20 +3095,20 @@ static void P_ProcessLineSpecial(line_t *line, mobj_t *mo, sector_t *callsec) { size_t linenum; side_t *setfront = &sides[line->sidenum[0]]; - side_t *setback = (line->args[3] && line->sidenum[1] != NO_SIDEDEF) ? &sides[line->sidenum[1]] : setfront; + side_t *setback = (args[3] && line->sidenum[1] != NO_SIDEDEF) ? &sides[line->sidenum[1]] : setfront; side_t *this; - boolean always = !(line->args[2]); // If args[2] is set: Only change mid texture if mid texture already exists on tagged lines, etc. + boolean always = !(args[2]); // If args[2] is set: Only change mid texture if mid texture already exists on tagged lines, etc. for (linenum = 0; linenum < numlines; linenum++) { if (lines[linenum].special == 439) continue; // Don't override other set texture lines! - if (!Tag_Find(&lines[linenum].tags, line->args[0])) + if (!Tag_Find(&lines[linenum].tags, args[0])) continue; // Find tagged lines // Front side - if (line->args[1] != TMSD_BACK) + if (args[1] != TMSD_BACK) { this = &sides[lines[linenum].sidenum[0]]; if (always || this->toptexture) this->toptexture = setfront->toptexture; @@ -2862,7 +3117,7 @@ static void P_ProcessLineSpecial(line_t *line, mobj_t *mo, sector_t *callsec) } // Back side - if (line->args[1] != TMSD_FRONT && lines[linenum].sidenum[1] != NO_SIDEDEF) + if (args[1] != TMSD_FRONT && lines[linenum].sidenum[1] != NO_SIDEDEF) { this = &sides[lines[linenum].sidenum[1]]; if (always || this->toptexture) this->toptexture = setback->toptexture; @@ -2880,7 +3135,7 @@ static void P_ProcessLineSpecial(line_t *line, mobj_t *mo, sector_t *callsec) case 441: // Trigger unlockable { - INT32 trigid = line->args[0]; + INT32 trigid = args[0]; if (trigid < 0 || trigid > 31) // limited by 32 bit variable CONS_Debug(DBG_GAMELOGIC, "Unlockable trigger (sidedef %u): bad trigger ID %d\n", line->sidenum[0], trigid); @@ -2905,22 +3160,22 @@ static void P_ProcessLineSpecial(line_t *line, mobj_t *mo, sector_t *callsec) case 442: // Calls P_SetMobjState on mobjs of a given type in the tagged sectors { - const mobjtype_t type = line->stringargs[0] ? get_number(line->stringargs[0]) : MT_NULL; + const mobjtype_t type = stringargs[0] ? get_number(stringargs[0]) : MT_NULL; statenum_t state = NUMSTATES; mobj_t *thing; if (type < 0 || type >= NUMMOBJTYPES) break; - if (!line->args[1]) + if (!args[1]) { - state = line->stringargs[1] ? get_number(line->stringargs[1]) : S_NULL; + state = stringargs[1] ? get_number(stringargs[1]) : S_NULL; if (state < 0 || state >= NUMSTATES) break; } - TAG_ITER_SECTORS(line->args[0], secnum) + TAG_ITER_SECTORS(args[0], secnum) { boolean tryagain; do { @@ -2930,7 +3185,7 @@ static void P_ProcessLineSpecial(line_t *line, mobj_t *mo, sector_t *callsec) if (thing->type != type) continue; - if (!P_SetMobjState(thing, line->args[1] ? thing->state->nextstate : state)) + if (!P_SetMobjState(thing, args[1] ? thing->state->nextstate : state)) { // mobj was removed tryagain = true; // snext is corrupt, we'll have to start over. break; @@ -2942,7 +3197,7 @@ static void P_ProcessLineSpecial(line_t *line, mobj_t *mo, sector_t *callsec) } case 443: // Calls a named Lua function - if (line->stringargs[0]) + if (stringargs[0]) LUA_HookLinedefExecute(line, mo, callsec); else CONS_Alert(CONS_WARNING, "Linedef %s is missing the hook name of the Lua function to call! (This should be given in stringarg0)\n", sizeu1(line-lines)); @@ -2950,9 +3205,9 @@ static void P_ProcessLineSpecial(line_t *line, mobj_t *mo, sector_t *callsec) case 444: // Earthquake camera { - quake.intensity = line->args[1] << FRACBITS; - quake.radius = line->args[2] << FRACBITS; - quake.time = line->args[0]; + quake.intensity = args[1] << FRACBITS; + quake.radius = args[2] << FRACBITS; + quake.time = args[0]; quake.epicenter = NULL; /// \todo @@ -2966,8 +3221,8 @@ static void P_ProcessLineSpecial(line_t *line, mobj_t *mo, sector_t *callsec) case 445: // Force block disappear remotely (reappear if args[2] is set) { - INT16 sectag = (INT16)(line->args[0]); - INT16 foftag = (INT16)(line->args[1]); + INT16 sectag = (INT16)(args[0]); + INT16 foftag = (INT16)(args[1]); sector_t *sec; // Sector that the FOF is visible (or not visible) in ffloor_t *rover; // FOF to vanish/un-vanish boolean foundrover = false; // for debug, "Can't find a FOF" message @@ -2980,7 +3235,7 @@ static void P_ProcessLineSpecial(line_t *line, mobj_t *mo, sector_t *callsec) if (!sec->ffloors) { CONS_Debug(DBG_GAMELOGIC, "Line type 445 Executor: Target sector #%d has no FOFs.\n", secnum); - return; + return false; } for (rover = sec->ffloors; rover; rover = rover->next) @@ -2992,7 +3247,7 @@ static void P_ProcessLineSpecial(line_t *line, mobj_t *mo, sector_t *callsec) oldflags = rover->fofflags; // Abracadabra! - if (line->args[2]) + if (args[2]) rover->fofflags |= FOF_EXISTS; else rover->fofflags &= ~FOF_EXISTS; @@ -3009,7 +3264,7 @@ static void P_ProcessLineSpecial(line_t *line, mobj_t *mo, sector_t *callsec) if (!foundrover) { CONS_Debug(DBG_GAMELOGIC, "Line type 445 Executor: Can't find a FOF control sector with tag %d\n", foftag); - return; + return false; } } } @@ -3017,8 +3272,8 @@ static void P_ProcessLineSpecial(line_t *line, mobj_t *mo, sector_t *callsec) case 446: // Make block fall remotely (acts like FOF_CRUMBLE) { - INT16 sectag = (INT16)(line->args[0]); - INT16 foftag = (INT16)(line->args[1]); + INT16 sectag = (INT16)(args[0]); + INT16 foftag = (INT16)(args[1]); sector_t *sec; // Sector that the FOF is visible in ffloor_t *rover; // FOF that we are going to make fall down boolean foundrover = false; // for debug, "Can't find a FOF" message @@ -3028,7 +3283,7 @@ static void P_ProcessLineSpecial(line_t *line, mobj_t *mo, sector_t *callsec) if (mo) // NULL check player = mo->player; - if (line->args[2] & TMFR_NORETURN) // don't respawn! + if (args[2] & TMFR_NORETURN) // don't respawn! respawn = false; TAG_ITER_SECTORS(sectag, secnum) @@ -3038,7 +3293,7 @@ static void P_ProcessLineSpecial(line_t *line, mobj_t *mo, sector_t *callsec) if (!sec->ffloors) { CONS_Debug(DBG_GAMELOGIC, "Line type 446 Executor: Target sector #%d has no FOFs.\n", secnum); - return; + return false; } for (rover = sec->ffloors; rover; rover = rover->next) @@ -3047,8 +3302,8 @@ static void P_ProcessLineSpecial(line_t *line, mobj_t *mo, sector_t *callsec) { foundrover = true; - if (line->args[2] & TMFR_CHECKFLAG) // FOF flags determine respawn ability instead? - respawn = !(rover->fofflags & FOF_NORETURN) ^ !!(line->args[2] & TMFR_NORETURN); // TMFR_NORETURN inverts + if (args[2] & TMFR_CHECKFLAG) // FOF flags determine respawn ability instead? + respawn = !(rover->fofflags & FOF_NORETURN) ^ !!(args[2] & TMFR_NORETURN); // TMFR_NORETURN inverts EV_StartCrumble(rover->master->frontsector, rover, (rover->fofflags & FOF_FLOATBOB), player, rover->alpha, respawn); } @@ -3057,7 +3312,7 @@ static void P_ProcessLineSpecial(line_t *line, mobj_t *mo, sector_t *callsec) if (!foundrover) { CONS_Debug(DBG_GAMELOGIC, "Line type 446 Executor: Can't find a FOF control sector with tag %d\n", foftag); - return; + return false; } } } @@ -3074,27 +3329,27 @@ static void P_ProcessLineSpecial(line_t *line, mobj_t *mo, sector_t *callsec) source = sides[line->sidenum[0]].colormap_data; else { - if (!line->args[1]) + if (!args[1]) source = line->frontsector->extra_colormap; else { - INT32 sourcesec = Tag_Iterate_Sectors(line->args[1], 0); + INT32 sourcesec = Tag_Iterate_Sectors(args[1], 0); if (sourcesec == -1) { - CONS_Debug(DBG_GAMELOGIC, "Line type 447 Executor: Can't find sector with source colormap (tag %d)!\n", line->args[1]); - return; + CONS_Debug(DBG_GAMELOGIC, "Line type 447 Executor: Can't find sector with source colormap (tag %d)!\n", args[1]); + return false; } source = sectors[sourcesec].extra_colormap; } } - TAG_ITER_SECTORS(line->args[0], secnum) + TAG_ITER_SECTORS(args[0], secnum) { if (sectors[secnum].colormap_protected) continue; P_ResetColormapFader(§ors[secnum]); - if (line->args[2] & TMCF_RELATIVE) + if (args[2] & TMCF_RELATIVE) { extracolormap_t *target = (!udmf && (line->flags & ML_TFERLINE) && line->sidenum[1] != NO_SIDEDEF) ? sides[line->sidenum[1]].colormap_data : sectors[secnum].extra_colormap; // use back colormap instead of target sector @@ -3102,17 +3357,17 @@ static void P_ProcessLineSpecial(line_t *line, mobj_t *mo, sector_t *callsec) extracolormap_t *exc = R_AddColormaps( target, source, - line->args[2] & TMCF_SUBLIGHTR, - line->args[2] & TMCF_SUBLIGHTG, - line->args[2] & TMCF_SUBLIGHTB, - line->args[2] & TMCF_SUBLIGHTA, - line->args[2] & TMCF_SUBFADER, - line->args[2] & TMCF_SUBFADEG, - line->args[2] & TMCF_SUBFADEB, - line->args[2] & TMCF_SUBFADEA, - line->args[2] & TMCF_SUBFADESTART, - line->args[2] & TMCF_SUBFADEEND, - line->args[2] & TMCF_IGNOREFLAGS, + args[2] & TMCF_SUBLIGHTR, + args[2] & TMCF_SUBLIGHTG, + args[2] & TMCF_SUBLIGHTB, + args[2] & TMCF_SUBLIGHTA, + args[2] & TMCF_SUBFADER, + args[2] & TMCF_SUBFADEG, + args[2] & TMCF_SUBFADEB, + args[2] & TMCF_SUBFADEA, + args[2] & TMCF_SUBFADESTART, + args[2] & TMCF_SUBFADEEND, + args[2] & TMCF_IGNOREFLAGS, false); if (!(sectors[secnum].extra_colormap = R_GetColormapFromList(exc))) @@ -3130,13 +3385,13 @@ static void P_ProcessLineSpecial(line_t *line, mobj_t *mo, sector_t *callsec) break; } case 448: // Change skybox viewpoint/centerpoint - if ((mo && mo->player && P_IsLocalPlayer(mo->player)) || line->args[3]) + if ((mo && mo->player && P_IsLocalPlayer(mo->player)) || args[3]) { - INT32 viewid = line->args[0]; - INT32 centerid = line->args[1]; + INT32 viewid = args[0]; + INT32 centerid = args[1]; // set viewpoint mobj - if (line->args[2] != TMS_CENTERPOINT) + if (args[2] != TMS_CENTERPOINT) { if (viewid >= 0 && viewid < 16) skyboxmo[0] = skyboxviewpnts[viewid]; @@ -3145,7 +3400,7 @@ static void P_ProcessLineSpecial(line_t *line, mobj_t *mo, sector_t *callsec) } // set centerpoint mobj - if (line->args[2] != TMS_VIEWPOINT) + if (args[2] != TMS_VIEWPOINT) { if (centerid >= 0 && centerid < 16) skyboxmo[1] = skyboxcenterpnts[centerid]; @@ -3156,14 +3411,14 @@ static void P_ProcessLineSpecial(line_t *line, mobj_t *mo, sector_t *callsec) CONS_Debug(DBG_GAMELOGIC, "Line type 448 Executor: viewid = %d, centerid = %d, viewpoint? = %s, centerpoint? = %s\n", viewid, centerid, - ((line->args[2] == TMS_CENTERPOINT) ? "no" : "yes"), - ((line->args[2] == TMS_VIEWPOINT) ? "no" : "yes")); + ((args[2] == TMS_CENTERPOINT) ? "no" : "yes"), + ((args[2] == TMS_VIEWPOINT) ? "no" : "yes")); } break; case 449: // Enable bosses with parameter { - INT32 bossid = line->args[0]; + INT32 bossid = args[0]; if (bossid & ~15) // if any bits other than first 16 are set { CONS_Alert(CONS_WARNING, @@ -3171,7 +3426,7 @@ static void P_ProcessLineSpecial(line_t *line, mobj_t *mo, sector_t *callsec) bossid); break; } - if (line->args[1]) + if (args[1]) { bossdisabled |= (1<<bossid); CONS_Debug(DBG_GAMELOGIC, "Line type 449 Executor: bossid disabled = %d", bossid); @@ -3185,13 +3440,13 @@ static void P_ProcessLineSpecial(line_t *line, mobj_t *mo, sector_t *callsec) } case 450: // Execute Linedef Executor - for recursion - P_LinedefExecute(line->args[0], mo, NULL); + P_LinedefExecute(args[0], mo, NULL); break; case 451: // Execute Random Linedef Executor { - INT32 rvalue1 = line->args[0]; - INT32 rvalue2 = line->args[1]; + INT32 rvalue1 = args[0]; + INT32 rvalue2 = args[1]; INT32 result; if (rvalue1 <= rvalue2) @@ -3205,9 +3460,9 @@ static void P_ProcessLineSpecial(line_t *line, mobj_t *mo, sector_t *callsec) case 452: // Set FOF alpha { - INT16 destvalue = (INT16)(line->args[2]); - INT16 sectag = (INT16)(line->args[0]); - INT16 foftag = (INT16)(line->args[1]); + INT16 destvalue = (INT16)(args[2]); + INT16 sectag = (INT16)(args[0]); + INT16 foftag = (INT16)(args[1]); sector_t *sec; // Sector that the FOF is visible in ffloor_t *rover; // FOF that we are going to operate boolean foundrover = false; // for debug, "Can't find a FOF" message @@ -3219,7 +3474,7 @@ static void P_ProcessLineSpecial(line_t *line, mobj_t *mo, sector_t *callsec) if (!sec->ffloors) { CONS_Debug(DBG_GAMELOGIC, "Line type 452 Executor: Target sector #%d has no FOFs.\n", secnum); - return; + return false; } for (rover = sec->ffloors; rover; rover = rover->next) @@ -3230,7 +3485,7 @@ static void P_ProcessLineSpecial(line_t *line, mobj_t *mo, sector_t *callsec) // If fading an invisible FOF whose render flags we did not yet set, // initialize its alpha to 0 for relative alpha calculation - if (!(line->args[3] & TMST_DONTDOTRANSLUCENT) && // do translucent + if (!(args[3] & TMST_DONTDOTRANSLUCENT) && // do translucent (rover->spawnflags & FOF_NOSHADE) && // do not include light blocks, which don't set FOF_NOSHADE !(rover->spawnflags & FOF_RENDERSIDES) && !(rover->spawnflags & FOF_RENDERPLANES) && @@ -3240,11 +3495,11 @@ static void P_ProcessLineSpecial(line_t *line, mobj_t *mo, sector_t *callsec) P_RemoveFakeFloorFader(rover); P_FadeFakeFloor(rover, rover->alpha, - max(0, min(255, (line->args[3] & TMST_RELATIVE) ? rover->alpha + destvalue : destvalue)), + max(0, min(255, (args[3] & TMST_RELATIVE) ? rover->alpha + destvalue : destvalue)), 0, // set alpha immediately false, NULL, // tic-based logic false, // do not handle FOF_EXISTS - !(line->args[3] & TMST_DONTDOTRANSLUCENT), // handle FOF_TRANSLUCENT + !(args[3] & TMST_DONTDOTRANSLUCENT), // handle FOF_TRANSLUCENT false, // do not handle lighting false, // do not handle colormap false, // do not handle collision @@ -3256,7 +3511,7 @@ static void P_ProcessLineSpecial(line_t *line, mobj_t *mo, sector_t *callsec) if (!foundrover) { CONS_Debug(DBG_GAMELOGIC, "Line type 452 Executor: Can't find a FOF control sector with tag %d\n", foftag); - return; + return false; } } break; @@ -3264,10 +3519,10 @@ static void P_ProcessLineSpecial(line_t *line, mobj_t *mo, sector_t *callsec) case 453: // Fade FOF { - INT16 destvalue = (INT16)(line->args[2]); - INT16 speed = (INT16)(line->args[3]); - INT16 sectag = (INT16)(line->args[0]); - INT16 foftag = (INT16)(line->args[1]); + INT16 destvalue = (INT16)(args[2]); + INT16 speed = (INT16)(args[3]); + INT16 sectag = (INT16)(args[0]); + INT16 foftag = (INT16)(args[1]); sector_t *sec; // Sector that the FOF is visible in ffloor_t *rover; // FOF that we are going to operate boolean foundrover = false; // for debug, "Can't find a FOF" message @@ -3280,7 +3535,7 @@ static void P_ProcessLineSpecial(line_t *line, mobj_t *mo, sector_t *callsec) if (!sec->ffloors) { CONS_Debug(DBG_GAMELOGIC, "Line type 453 Executor: Target sector #%d has no FOFs.\n", secnum); - return; + return false; } for (rover = sec->ffloors; rover; rover = rover->next) @@ -3290,7 +3545,7 @@ static void P_ProcessLineSpecial(line_t *line, mobj_t *mo, sector_t *callsec) foundrover = true; // Prevent continuous execs from interfering on an existing fade - if (!(line->args[4] & TMFT_OVERRIDE) + if (!(args[4] & TMFT_OVERRIDE) && rover->fadingdata) //&& ((fade_t*)rover->fadingdata)->timer > (ticbased ? 2 : speed*2)) { @@ -3302,20 +3557,20 @@ static void P_ProcessLineSpecial(line_t *line, mobj_t *mo, sector_t *callsec) P_AddFakeFloorFader(rover, secnum, j, destvalue, speed, - (line->args[4] & TMFT_TICBASED), // tic-based logic - (line->args[4] & TMFT_RELATIVE), // Relative destvalue - !(line->args[4] & TMFT_DONTDOEXISTS), // do not handle FOF_EXISTS - !(line->args[4] & TMFT_DONTDOTRANSLUCENT), // do not handle FOF_TRANSLUCENT - !(line->args[4] & TMFT_DONTDOLIGHTING), // do not handle lighting - !(line->args[4] & TMFT_DONTDOCOLORMAP), // do not handle colormap - !(line->args[4] & TMFT_IGNORECOLLISION), // do not handle collision - (line->args[4] & TMFT_GHOSTFADE), // do ghost fade (no collision during fade) - (line->args[4] & TMFT_USEEXACTALPHA)); // use exact alpha values (for opengl) + (args[4] & TMFT_TICBASED), // tic-based logic + (args[4] & TMFT_RELATIVE), // Relative destvalue + !(args[4] & TMFT_DONTDOEXISTS), // do not handle FOF_EXISTS + !(args[4] & TMFT_DONTDOTRANSLUCENT), // do not handle FOF_TRANSLUCENT + !(args[4] & TMFT_DONTDOLIGHTING), // do not handle lighting + !(args[4] & TMFT_DONTDOCOLORMAP), // do not handle colormap + !(args[4] & TMFT_IGNORECOLLISION), // do not handle collision + (args[4] & TMFT_GHOSTFADE), // do ghost fade (no collision during fade) + (args[4] & TMFT_USEEXACTALPHA)); // use exact alpha values (for opengl) else { // If fading an invisible FOF whose render flags we did not yet set, // initialize its alpha to 1 for relative alpha calculation - if (!(line->args[4] & TMFT_DONTDOTRANSLUCENT) && // do translucent + if (!(args[4] & TMFT_DONTDOTRANSLUCENT) && // do translucent (rover->spawnflags & FOF_NOSHADE) && // do not include light blocks, which don't set FOF_NOSHADE !(rover->spawnflags & FOF_RENDERSIDES) && !(rover->spawnflags & FOF_RENDERPLANES) && @@ -3325,16 +3580,16 @@ static void P_ProcessLineSpecial(line_t *line, mobj_t *mo, sector_t *callsec) P_RemoveFakeFloorFader(rover); P_FadeFakeFloor(rover, rover->alpha, - max(0, min(255, (line->args[4] & TMFT_RELATIVE) ? rover->alpha + destvalue : destvalue)), + max(0, min(255, (args[4] & TMFT_RELATIVE) ? rover->alpha + destvalue : destvalue)), 0, // set alpha immediately false, NULL, // tic-based logic - !(line->args[4] & TMFT_DONTDOEXISTS), // do not handle FOF_EXISTS - !(line->args[4] & TMFT_DONTDOTRANSLUCENT), // do not handle FOF_TRANSLUCENT - !(line->args[4] & TMFT_DONTDOLIGHTING), // do not handle lighting - !(line->args[4] & TMFT_DONTDOCOLORMAP), // do not handle colormap - !(line->args[4] & TMFT_IGNORECOLLISION), // do not handle collision - (line->args[4] & TMFT_GHOSTFADE), // do ghost fade (no collision during fade) - (line->args[4] & TMFT_USEEXACTALPHA)); // use exact alpha values (for opengl) + !(args[4] & TMFT_DONTDOEXISTS), // do not handle FOF_EXISTS + !(args[4] & TMFT_DONTDOTRANSLUCENT), // do not handle FOF_TRANSLUCENT + !(args[4] & TMFT_DONTDOLIGHTING), // do not handle lighting + !(args[4] & TMFT_DONTDOCOLORMAP), // do not handle colormap + !(args[4] & TMFT_IGNORECOLLISION), // do not handle collision + (args[4] & TMFT_GHOSTFADE), // do ghost fade (no collision during fade) + (args[4] & TMFT_USEEXACTALPHA)); // use exact alpha values (for opengl) } } j++; @@ -3343,7 +3598,7 @@ static void P_ProcessLineSpecial(line_t *line, mobj_t *mo, sector_t *callsec) if (!foundrover) { CONS_Debug(DBG_GAMELOGIC, "Line type 453 Executor: Can't find a FOF control sector with tag %d\n", foftag); - return; + return false; } } break; @@ -3351,8 +3606,8 @@ static void P_ProcessLineSpecial(line_t *line, mobj_t *mo, sector_t *callsec) case 454: // Stop fading FOF { - INT16 sectag = (INT16)(line->args[0]); - INT16 foftag = (INT16)(line->args[1]); + INT16 sectag = (INT16)(args[0]); + INT16 foftag = (INT16)(args[1]); sector_t *sec; // Sector that the FOF is visible in ffloor_t *rover; // FOF that we are going to operate boolean foundrover = false; // for debug, "Can't find a FOF" message @@ -3364,7 +3619,7 @@ static void P_ProcessLineSpecial(line_t *line, mobj_t *mo, sector_t *callsec) if (!sec->ffloors) { CONS_Debug(DBG_GAMELOGIC, "Line type 454 Executor: Target sector #%d has no FOFs.\n", secnum); - return; + return false; } for (rover = sec->ffloors; rover; rover = rover->next) @@ -3374,14 +3629,14 @@ static void P_ProcessLineSpecial(line_t *line, mobj_t *mo, sector_t *callsec) foundrover = true; P_ResetFakeFloorFader(rover, NULL, - !(line->args[2])); // do not finalize collision flags + !(args[2])); // do not finalize collision flags } } if (!foundrover) { CONS_Debug(DBG_GAMELOGIC, "Line type 454 Executor: Can't find a FOF control sector with tag %d\n", foftag); - return; + return false; } } break; @@ -3394,21 +3649,21 @@ static void P_ProcessLineSpecial(line_t *line, mobj_t *mo, sector_t *callsec) dest = sides[line->sidenum[0]].colormap_data; else { - if (!line->args[1]) + if (!args[1]) dest = line->frontsector->extra_colormap; else { - INT32 destsec = Tag_Iterate_Sectors(line->args[1], 0); + INT32 destsec = Tag_Iterate_Sectors(args[1], 0); if (destsec == -1) { - CONS_Debug(DBG_GAMELOGIC, "Line type 455 Executor: Can't find sector with destination colormap (tag %d)!\n", line->args[1]); - return; + CONS_Debug(DBG_GAMELOGIC, "Line type 455 Executor: Can't find sector with destination colormap (tag %d)!\n", args[1]); + return false; } dest = sectors[destsec].extra_colormap; } } - TAG_ITER_SECTORS(line->args[0], secnum) + TAG_ITER_SECTORS(args[0], secnum) { extracolormap_t *source_exc, *dest_exc, *exc; @@ -3416,7 +3671,7 @@ static void P_ProcessLineSpecial(line_t *line, mobj_t *mo, sector_t *callsec) continue; // Don't interrupt ongoing fade - if (!(line->args[3] & TMCF_OVERRIDE) + if (!(args[3] & TMCF_OVERRIDE) && sectors[secnum].fadecolormapdata) //&& ((fadecolormap_t*)sectors[secnum].fadecolormapdata)->timer > (ticbased ? 2 : speed*2)) { @@ -3430,7 +3685,7 @@ static void P_ProcessLineSpecial(line_t *line, mobj_t *mo, sector_t *callsec) exc = sectors[secnum].extra_colormap; - if (!(line->args[3] & TMCF_FROMBLACK) // Override fade from default rgba + if (!(args[3] & TMCF_FROMBLACK) // Override fade from default rgba && !R_CheckDefaultColormap(dest, true, false, false) && R_CheckDefaultColormap(exc, true, false, false)) { @@ -3452,22 +3707,22 @@ static void P_ProcessLineSpecial(line_t *line, mobj_t *mo, sector_t *callsec) else source_exc = exc ? exc : R_GetDefaultColormap(); - if (line->args[3] & TMCF_RELATIVE) + if (args[3] & TMCF_RELATIVE) { exc = R_AddColormaps( source_exc, dest, - line->args[3] & TMCF_SUBLIGHTR, - line->args[3] & TMCF_SUBLIGHTG, - line->args[3] & TMCF_SUBLIGHTB, - line->args[3] & TMCF_SUBLIGHTA, - line->args[3] & TMCF_SUBFADER, - line->args[3] & TMCF_SUBFADEG, - line->args[3] & TMCF_SUBFADEB, - line->args[3] & TMCF_SUBFADEA, - line->args[3] & TMCF_SUBFADESTART, - line->args[3] & TMCF_SUBFADEEND, - line->args[3] & TMCF_IGNOREFLAGS, + args[3] & TMCF_SUBLIGHTR, + args[3] & TMCF_SUBLIGHTG, + args[3] & TMCF_SUBLIGHTB, + args[3] & TMCF_SUBLIGHTA, + args[3] & TMCF_SUBFADER, + args[3] & TMCF_SUBFADEG, + args[3] & TMCF_SUBFADEB, + args[3] & TMCF_SUBFADEA, + args[3] & TMCF_SUBFADESTART, + args[3] & TMCF_SUBFADEEND, + args[3] & TMCF_IGNOREFLAGS, false); } else @@ -3483,27 +3738,27 @@ static void P_ProcessLineSpecial(line_t *line, mobj_t *mo, sector_t *callsec) Z_Free(exc); Add_ColormapFader(§ors[secnum], source_exc, dest_exc, true, // tic-based timing - line->args[2]); + args[2]); } break; } case 456: // Stop fade colormap - TAG_ITER_SECTORS(line->args[0], secnum) + TAG_ITER_SECTORS(args[0], secnum) P_ResetColormapFader(§ors[secnum]); break; case 457: // Track mobj angle to point if (mo) { - INT32 failureangle = FixedAngle((min(max(abs(line->args[1]), 0), 360))*FRACUNIT); - INT32 failuredelay = abs(line->args[2]); - INT32 failureexectag = line->args[3]; - boolean persist = !!(line->args[4]); + INT32 failureangle = FixedAngle((min(max(abs(args[1]), 0), 360))*FRACUNIT); + INT32 failuredelay = abs(args[2]); + INT32 failureexectag = args[3]; + boolean persist = !!(args[4]); mobj_t *anchormo; - anchormo = P_FindObjectTypeFromTag(MT_ANGLEMAN, line->args[0]); + anchormo = P_FindObjectTypeFromTag(MT_ANGLEMAN, args[0]); if (!anchormo) - return; + return false; mo->eflags |= MFE_TRACERANGLE; P_SetTarget(&mo->tracer, anchormo); @@ -3527,24 +3782,24 @@ static void P_ProcessLineSpecial(line_t *line, mobj_t *mo, sector_t *callsec) // console player only if (mo && mo->player && P_IsLocalPlayer(mo->player) && (!bot || bot != mo)) { - INT32 promptnum = max(0, line->args[0] - 1); - INT32 pagenum = max(0, line->args[1] - 1); - INT32 postexectag = abs(line->args[3]); - - boolean closetextprompt = (line->args[2] & TMP_CLOSE); - //boolean allplayers = (line->args[2] & TMP_ALLPLAYERS); - boolean runpostexec = (line->args[2] & TMP_RUNPOSTEXEC); - boolean blockcontrols = !(line->args[2] & TMP_KEEPCONTROLS); - boolean freezerealtime = !(line->args[2] & TMP_KEEPREALTIME); - //boolean freezethinkers = (line->args[2] & TMP_FREEZETHINKERS); - boolean callbynamedtag = (line->args[2] & TMP_CALLBYNAME); + INT32 promptnum = max(0, args[0] - 1); + INT32 pagenum = max(0, args[1] - 1); + INT32 postexectag = abs(args[3]); + + boolean closetextprompt = (args[2] & TMP_CLOSE); + //boolean allplayers = (args[2] & TMP_ALLPLAYERS); + boolean runpostexec = (args[2] & TMP_RUNPOSTEXEC); + boolean blockcontrols = !(args[2] & TMP_KEEPCONTROLS); + boolean freezerealtime = !(args[2] & TMP_KEEPREALTIME); + //boolean freezethinkers = (args[2] & TMP_FREEZETHINKERS); + boolean callbynamedtag = (args[2] & TMP_CALLBYNAME); if (closetextprompt) F_EndTextPrompt(false, false); else { - if (callbynamedtag && line->stringargs[0] && line->stringargs[0][0]) - F_GetPromptPageByNamedTag(line->stringargs[0], &promptnum, &pagenum); + if (callbynamedtag && stringargs[0] && stringargs[0][0]) + F_GetPromptPageByNamedTag(stringargs[0], &promptnum, &pagenum); F_StartTextPrompt(promptnum, pagenum, mo, runpostexec ? postexectag : 0, blockcontrols, freezerealtime); } } @@ -3552,8 +3807,8 @@ static void P_ProcessLineSpecial(line_t *line, mobj_t *mo, sector_t *callsec) case 460: // Award rings { - INT16 rings = line->args[0]; - INT32 delay = line->args[1]; + INT16 rings = args[0]; + INT32 delay = args[1]; if (mo && mo->player) { if (delay <= 0 || !(leveltime % delay)) @@ -3564,28 +3819,28 @@ static void P_ProcessLineSpecial(line_t *line, mobj_t *mo, sector_t *callsec) case 461: // Spawns an object on the map based on texture offsets { - const mobjtype_t type = line->stringargs[0] ? get_number(line->stringargs[0]) : MT_NULL; + const mobjtype_t type = stringargs[0] ? get_number(stringargs[0]) : MT_NULL; mobj_t *mobj; fixed_t x, y, z; - if (line->args[4]) // If args[4] is set, spawn randomly within a range + if (args[4]) // If args[4] is set, spawn randomly within a range { - x = P_RandomRange(line->args[0], line->args[5])<<FRACBITS; - y = P_RandomRange(line->args[1], line->args[6])<<FRACBITS; - z = P_RandomRange(line->args[2], line->args[7])<<FRACBITS; + x = P_RandomRange(args[0], args[5])<<FRACBITS; + y = P_RandomRange(args[1], args[6])<<FRACBITS; + z = P_RandomRange(args[2], args[7])<<FRACBITS; } else { - x = line->args[0] << FRACBITS; - y = line->args[1] << FRACBITS; - z = line->args[2] << FRACBITS; + x = args[0] << FRACBITS; + y = args[1] << FRACBITS; + z = args[2] << FRACBITS; } mobj = P_SpawnMobj(x, y, z, type); if (mobj) { - mobj->angle = FixedAngle(line->args[3] << FRACBITS); + mobj->angle = FixedAngle(args[3] << FRACBITS); CONS_Debug(DBG_GAMELOGIC, "Linedef Type %d - Spawn Object: %d spawned at (%d, %d, %d)\n", line->special, mobj->type, mobj->x>>FRACBITS, mobj->y>>FRACBITS, mobj->z>>FRACBITS); //TODO: Convert mobj->type to a string somehow. } else @@ -3615,10 +3870,10 @@ static void P_ProcessLineSpecial(line_t *line, mobj_t *mo, sector_t *callsec) { if (mo) { - INT32 color = line->stringargs[0] ? get_number(line->stringargs[0]) : SKINCOLOR_NONE; + INT32 color = stringargs[0] ? get_number(stringargs[0]) : SKINCOLOR_NONE; if (color < 0 || color >= numskincolors) - return; + return false; var1 = 0; var2 = color; @@ -3634,7 +3889,7 @@ static void P_ProcessLineSpecial(line_t *line, mobj_t *mo, sector_t *callsec) // Find the center of the Eggtrap and release all the pretty animals! // The chimps are my friends.. heeheeheheehehee..... - LouisJM - TAG_ITER_THINGS(line->args[0], mtnum) + TAG_ITER_THINGS(args[0], mtnum) { mo2 = mapthings[mtnum].mobj; @@ -3650,7 +3905,7 @@ static void P_ProcessLineSpecial(line_t *line, mobj_t *mo, sector_t *callsec) P_KillMobj(mo2, NULL, mo, 0); } - if (!(line->args[1])) + if (!(args[1])) { INT32 i; @@ -3672,19 +3927,19 @@ static void P_ProcessLineSpecial(line_t *line, mobj_t *mo, sector_t *callsec) if (!udmf) break; - TAG_ITER_LINES(line->args[0], linenum) + TAG_ITER_LINES(args[0], linenum) { - if (line->args[2]) - lines[linenum].executordelay += line->args[1]; + if (args[2]) + lines[linenum].executordelay += args[1]; else - lines[linenum].executordelay = line->args[1]; + lines[linenum].executordelay = args[1]; } } break; case 466: // Set level failure state { - if (line->args[1]) + if (args[1]) { stagefailed = false; CONS_Debug(DBG_GAMELOGIC, "Stage can be completed successfully!\n"); @@ -3698,7 +3953,7 @@ static void P_ProcessLineSpecial(line_t *line, mobj_t *mo, sector_t *callsec) break; case 467: // Set light level - TAG_ITER_SECTORS(line->args[0], secnum) + TAG_ITER_SECTORS(args[0], secnum) { if (sectors[secnum].lightingdata) { @@ -3707,26 +3962,26 @@ static void P_ProcessLineSpecial(line_t *line, mobj_t *mo, sector_t *callsec) sectors[secnum].lightingdata = NULL; } - if (line->args[2] == TML_FLOOR) + if (args[2] == TML_FLOOR) { - if (line->args[3]) - sectors[secnum].floorlightlevel += line->args[1]; + if (args[3]) + sectors[secnum].floorlightlevel += args[1]; else - sectors[secnum].floorlightlevel = line->args[1]; + sectors[secnum].floorlightlevel = args[1]; } - else if (line->args[2] == TML_CEILING) + else if (args[2] == TML_CEILING) { - if (line->args[3]) - sectors[secnum].ceilinglightlevel += line->args[1]; + if (args[3]) + sectors[secnum].ceilinglightlevel += args[1]; else - sectors[secnum].ceilinglightlevel = line->args[1]; + sectors[secnum].ceilinglightlevel = args[1]; } else { - if (line->args[3]) - sectors[secnum].lightlevel += line->args[1]; + if (args[3]) + sectors[secnum].lightlevel += args[1]; else - sectors[secnum].lightlevel = line->args[1]; + sectors[secnum].lightlevel = args[1]; sectors[secnum].lightlevel = max(0, min(255, sectors[secnum].lightlevel)); } } @@ -3739,18 +3994,18 @@ static void P_ProcessLineSpecial(line_t *line, mobj_t *mo, sector_t *callsec) if (!udmf) break; - if (line->args[1] < 0 || line->args[1] >= NUMLINEARGS) + if (args[1] < 0 || args[1] >= NUM_SCRIPT_ARGS) { - CONS_Debug(DBG_GAMELOGIC, "Linedef type 468: Invalid linedef arg %d\n", line->args[1]); + CONS_Debug(DBG_GAMELOGIC, "Linedef type 468: Invalid linedef arg %d\n", args[1]); break; } - TAG_ITER_LINES(line->args[0], linenum) + TAG_ITER_LINES(args[0], linenum) { - if (line->args[3]) - lines[linenum].args[line->args[1]] += line->args[2]; + if (args[3]) + lines[linenum].args[args[1]] += args[2]; else - lines[linenum].args[line->args[1]] = line->args[2]; + lines[linenum].args[args[1]] = args[2]; } } break; @@ -3762,29 +4017,70 @@ static void P_ProcessLineSpecial(line_t *line, mobj_t *mo, sector_t *callsec) if (!udmf) break; - if (!line->stringargs[0]) + if (!stringargs[0]) break; - gravityvalue = FloatToFixed(atof(line->stringargs[0])); + gravityvalue = FloatToFixed(atof(stringargs[0])); - TAG_ITER_SECTORS(line->args[0], secnum) + TAG_ITER_SECTORS(args[0], secnum) { - if (line->args[1]) + if (args[1]) sectors[secnum].gravity = FixedMul(sectors[secnum].gravity, gravityvalue); else sectors[secnum].gravity = gravityvalue; - if (line->args[2] == TMF_ADD) + if (args[2] == TMF_ADD) sectors[secnum].flags |= MSF_GRAVITYFLIP; - else if (line->args[2] == TMF_REMOVE) + else if (args[2] == TMF_REMOVE) sectors[secnum].flags &= ~MSF_GRAVITYFLIP; - if (line->args[3]) + if (args[3]) sectors[secnum].specialflags |= SSF_GRAVITYOVERRIDE; } } break; + case 475: // ACS_Execute + { + if (!stringargs[0]) + { + CONS_Debug(DBG_GAMELOGIC, "Linedef type 475: No script name given\n"); + return false; + } + + ACS_Execute(stringargs[0], &args[1], NUM_SCRIPT_ARGS - 1, (const char* const*)&stringargs[1], NUM_SCRIPT_STRINGARGS - 1, activator); + } + break; + case 476: // ACS_ExecuteAlways + { + if (!stringargs[0]) + { + CONS_Debug(DBG_GAMELOGIC, "Linedef type 476: No script name given\n"); + return false; + } + + ACS_ExecuteAlways(stringargs[0], &args[1], NUM_SCRIPT_ARGS - 1, (const char* const*)&stringargs[1], NUM_SCRIPT_STRINGARGS - 1, activator); + } + break; + case 477: // ACS_Suspend + if (!stringargs[0]) + { + CONS_Debug(DBG_GAMELOGIC, "Linedef type 477: No script name given\n"); + return false; + } + + ACS_Suspend(stringargs[0]); + break; + case 478: // ACS_Terminate + if (!stringargs[0]) + { + CONS_Debug(DBG_GAMELOGIC, "Linedef type 478: No script name given\n"); + return false; + } + + ACS_Terminate(stringargs[0]); + break; + case 480: // Polyobj_DoorSlide case 481: // Polyobj_DoorSwing PolyDoor(line); @@ -3811,6 +4107,8 @@ static void P_ProcessLineSpecial(line_t *line, mobj_t *mo, sector_t *callsec) default: break; } + + return true; } // @@ -5328,6 +5626,328 @@ void P_CheckMobjTrigger(mobj_t *mobj, boolean pushable) P_CheckMobjSectorTrigger(mobj, originalsector); } +static void P_SectorActionWasActivated(sector_t *sec) +{ + if ((sec->activation & SECSPAC_TRIGGERMASK) == SECSPAC_ONCESPECIAL) + { + sec->action = 0; + } +} + +static boolean P_SectorActionIsContinuous(sector_t *sec) +{ + return ((sec->activation & SECSPAC_TRIGGERMASK) == SECSPAC_CONTINUOUSSPECIAL); +} + +static boolean P_AllowSpecialEnter(sector_t *sec, mobj_t *thing) +{ + if (thing->player != NULL) + { + return !!(sec->activation & SECSPAC_ENTER); + } + else if ((thing->flags & (MF_ENEMY|MF_BOSS)) != 0) + { + return !!(sec->activation & SECSPAC_ENTERMONSTER); + } + else if (thing->flags & MF_MISSILE) + { + return !!(sec->activation & SECSPAC_ENTERMISSILE); + } + + // No activation flags for you. + return false; +} + +static boolean P_AllowSpecialFloor(sector_t *sec, mobj_t *thing) +{ + if (thing->player != NULL) + { + return !!(sec->activation & SECSPAC_FLOOR); + } + else if ((thing->flags & (MF_ENEMY|MF_BOSS)) != 0) + { + return !!(sec->activation & SECSPAC_FLOORMONSTER); + } + else if (thing->flags & MF_MISSILE) + { + return !!(sec->activation & SECSPAC_FLOORMISSILE); + } + + // No activation flags for you. + return false; +} + +static boolean P_AllowSpecialCeiling(sector_t *sec, mobj_t *thing) +{ + if (thing->player != NULL) + { + return !!(sec->activation & SECSPAC_CEILING); + } + else if ((thing->flags & (MF_ENEMY|MF_BOSS)) != 0) + { + return !!(sec->activation & SECSPAC_CEILINGMONSTER); + } + else if (thing->flags & MF_MISSILE) + { + return !!(sec->activation & SECSPAC_CEILINGMISSILE); + } + + // No activation flags for you. + return false; +} + +static void P_CheckMobj3DFloorAction(mobj_t *mo, sector_t *sec, boolean continuous, boolean sectorchanged) +{ + sector_t *originalsector = mo->subsector->sector; + ffloor_t *rover; + sector_t *roversec; + + activator_t *activator = NULL; + boolean result = false; + + for (rover = sec->ffloors; rover; rover = rover->next) + { + fixed_t top = INT32_MIN; + fixed_t bottom = INT32_MAX; + fixed_t mid = 0; + + roversec = rover->master->frontsector; + + if (P_SectorActionIsContinuous(roversec) != continuous) + { + // Does not match continuous state. + continue; + } + + if (P_CanActivateSpecial(roversec->action) == false) + { + // No special to even activate. + continue; + } + + top = P_GetSpecialTopZ(mo, roversec, sec); + bottom = P_GetSpecialBottomZ(mo, roversec, sec); + mid = bottom + ((top - bottom) / 2); + + if (mo->z > top || mo->z + mo->height < bottom) + { + // Out of bounds. + continue; + } + + if (P_AllowSpecialEnter(roversec, mo) == false) + { + boolean floor = false; + boolean ceiling = false; + + if (P_AllowSpecialFloor(roversec, mo) == true) + { + floor = (P_GetMobjFeet(mo) >= mid); + } + + if (P_AllowSpecialCeiling(roversec, mo) == true) + { + ceiling = (P_GetMobjHead(mo) <= mid); + } + + if (floor == false && ceiling == false) + { + continue; + } + } + else if (sectorchanged == false) + { + continue; + } + + activator = Z_Calloc(sizeof(activator_t), PU_LEVEL, NULL); + I_Assert(activator != NULL); + + P_SetTarget(&activator->mo, mo); + activator->sector = roversec; + + result = P_ProcessSpecial(activator, roversec->action, roversec->args, roversec->stringargs); + + P_SetTarget(&activator->mo, NULL); + Z_Free(activator); + + if (result == true) + { + P_SectorActionWasActivated(roversec); + } + + if TELEPORTED(mo) return; + } +} + +static void P_CheckMobjPolyobjAction(mobj_t *mo, boolean continuous, boolean sectorchanged) +{ + sector_t *originalsector = mo->subsector->sector; + polyobj_t *po; + sector_t *polysec; + boolean touching = false; + boolean inside = false; + + activator_t *activator = NULL; + boolean result = false; + + for (po = mo->subsector->polyList; po; po = (polyobj_t *)(po->link.next)) + { + polysec = po->lines[0]->backsector; + + if (P_SectorActionIsContinuous(polysec) != continuous) + { + // Does not match continuous state. + continue; + } + + if (P_CanActivateSpecial(polysec->action) == false) + { + // No special to even activate. + continue; + } + + touching = P_MobjTouchingPolyobj(po, mo); + inside = P_MobjInsidePolyobj(po, mo); + + if (!(inside || touching)) + { + continue; + } + + if (P_AllowSpecialEnter(polysec, mo) == false) + { + boolean floor = false; + boolean ceiling = false; + + if (P_AllowSpecialFloor(polysec, mo) == true) + { + floor = (P_GetMobjFeet(mo) == P_GetSpecialTopZ(mo, polysec, polysec)); + } + + if (P_AllowSpecialCeiling(polysec, mo) == true) + { + ceiling = (P_GetMobjHead(mo) == P_GetSpecialBottomZ(mo, polysec, polysec)); + } + + if (floor == false && ceiling == false) + { + continue; + } + } + else if (sectorchanged == false) + { + continue; + } + + activator = Z_Calloc(sizeof(activator_t), PU_LEVEL, NULL); + I_Assert(activator != NULL); + + P_SetTarget(&activator->mo, mo); + activator->sector = polysec; + + result = P_ProcessSpecial(activator, polysec->action, polysec->args, polysec->stringargs); + + P_SetTarget(&activator->mo, NULL); + Z_Free(activator); + + if (result == true) + { + P_SectorActionWasActivated(polysec); + } + + if TELEPORTED(mo) return; + } +} + +static void P_CheckMobjSectorAction(mobj_t *mo, sector_t *sec, boolean continuous, boolean sectorchanged) +{ + activator_t *activator = NULL; + boolean result = false; + + if (P_SectorActionIsContinuous(sec) != continuous) + { + // Does not match continuous state. + return; + } + + if (P_CanActivateSpecial(sec->action) == false) + { + // No special to even activate. + return; + } + + if (P_AllowSpecialEnter(sec, mo) == false) + { + boolean floor = false; + boolean ceiling = false; + + if (P_AllowSpecialFloor(sec, mo) == true) + { + floor = (P_GetMobjFeet(mo) == P_GetSpecialBottomZ(mo, sec, sec)); + } + + if (P_AllowSpecialCeiling(sec, mo) == true) + { + ceiling = (P_GetMobjHead(mo) == P_GetSpecialTopZ(mo, sec, sec)); + } + + if (floor == false && ceiling == false) + { + return; + } + } + else if (sectorchanged == false) + { + return; + } + + activator = Z_Calloc(sizeof(activator_t), PU_LEVEL, NULL); + I_Assert(activator != NULL); + + P_SetTarget(&activator->mo, mo); + activator->sector = sec; + + result = P_ProcessSpecial(activator, sec->action, sec->args, sec->stringargs); + + P_SetTarget(&activator->mo, NULL); + Z_Free(activator); + + if (result == true) + { + P_SectorActionWasActivated(sec); + } +} + +void P_CheckMobjTouchingSectorActions(mobj_t *mobj, boolean continuous, boolean sectorchanged) +{ + sector_t *originalsector; + + if (mobj->subsector == NULL) + { + return; + } + + originalsector = mobj->subsector->sector; + + if (mobj->player != NULL) + { + if (mobj->player->spectator == true) + { + // Ignore spectators. + return; + } + } + + P_CheckMobj3DFloorAction(mobj, originalsector, continuous, sectorchanged); + if TELEPORTED(mobj) return; + + P_CheckMobjPolyobjAction(mobj, continuous, sectorchanged); + if TELEPORTED(mobj) return; + + P_CheckMobjSectorAction(mobj, originalsector, continuous, sectorchanged); +} + #undef TELEPORTED /** Animate planes, scroll walls, etc. and keeps track of level timelimit and exits if time is up. @@ -8945,3 +9565,21 @@ static void P_SpawnPushers(void) } } } + +void P_CheckSectorTransitionalEffects(mobj_t *thing, sector_t *prevsec, boolean wasgrounded) +{ + if (!udmf) + { + return; + } + + boolean sectorchanged = (prevsec != thing->subsector->sector); + + if (!sectorchanged && wasgrounded == P_IsObjectOnGround(thing)) + { + return; + } + + // Check for each time / once sector special actions + P_CheckMobjTouchingSectorActions(thing, false, sectorchanged); +} diff --git a/src/p_spec.h b/src/p_spec.h index b9c3a2ca3c2055481b54cd2d2fce3217df244fe7..365738e885b838f115c388e26cd39103476128e8 100644 --- a/src/p_spec.h +++ b/src/p_spec.h @@ -2,7 +2,7 @@ //----------------------------------------------------------------------------- // Copyright (C) 1993-1996 by id Software, Inc. // Copyright (C) 1998-2000 by DooM Legacy Team. -// Copyright (C) 1999-2023 by Sonic Team Junior. +// Copyright (C) 1999-2024 by Sonic Team Junior. // // This program is free software distributed under the // terms of the GNU General Public License, version 2. @@ -17,6 +17,12 @@ #ifndef __P_SPEC__ #define __P_SPEC__ +#ifdef __cplusplus +extern "C" { +#endif + +#include "r_defs.h" + extern mobj_t *skyboxmo[2]; // current skybox mobjs: 0 = viewpoint, 1 = centerpoint extern mobj_t *skyboxviewpnts[16]; // array of MT_SKYBOX viewpoint mobjs extern mobj_t *skyboxcenterpnts[16]; // array of MT_SKYBOX centerpoint mobjs @@ -517,10 +523,12 @@ sector_t *P_PlayerTouchingSectorSpecial(player_t *player, INT32 section, INT32 n sector_t *P_PlayerTouchingSectorSpecialFlag(player_t *player, sectorspecialflags_t flag); void P_PlayerInSpecialSector(player_t *player); void P_CheckMobjTrigger(mobj_t *mobj, boolean pushable); +void P_CheckMobjTouchingSectorActions(mobj_t *mobj, boolean continuous, boolean sectorchanged); sector_t *P_FindPlayerTrigger(player_t *player, line_t *sourceline); boolean P_IsPlayerValid(size_t playernum); boolean P_CanPlayerTrigger(size_t playernum); void P_ProcessSpecialSector(player_t *player, sector_t *sector, sector_t *roversector); +void P_CheckSectorTransitionalEffects(mobj_t *thing, sector_t *prevsec, boolean wasgrounded); fixed_t P_FindLowestFloorSurrounding(sector_t *sec); fixed_t P_FindHighestFloorSurrounding(sector_t *sec); @@ -533,6 +541,28 @@ fixed_t P_FindHighestCeilingSurrounding(sector_t *sec); INT32 P_FindMinSurroundingLight(sector_t *sector, INT32 max); +void P_CrossSpecialLine(line_t *line, INT32 side, mobj_t *thing); +void P_PushSpecialLine(line_t *line, mobj_t *thing); +void P_ActivateThingSpecial(mobj_t *mo, mobj_t *source); + +mobj_t* P_FindObjectTypeFromTag(mobjtype_t type, mtag_t tag); + +// +// Special activation info +// +typedef struct +{ + mobj_t *mo; + line_t *line; + UINT8 side; + sector_t *sector; + polyobj_t *po; + boolean fromLineSpecial; // Backwards compat for ACS +} activator_t; + +boolean P_CanActivateSpecial(INT16 special); +boolean P_ProcessSpecial(activator_t *activator, INT16 special, INT32 *args, char **stringargs); + void P_SetupSignExit(player_t *player); boolean P_IsFlagAtBase(mobjtype_t flag); @@ -1117,4 +1147,8 @@ void T_PlaneDisplace(planedisplace_t *pd); void P_CalcHeight(player_t *player); +#ifdef __cplusplus +} // extern "C" +#endif + #endif diff --git a/src/p_tick.c b/src/p_tick.c index 56e0fd897bfbba718885a1104224d1554790f227..d792c7b8c505e2ebefeff715c02056dff79214a0 100644 --- a/src/p_tick.c +++ b/src/p_tick.c @@ -2,7 +2,7 @@ //----------------------------------------------------------------------------- // Copyright (C) 1993-1996 by id Software, Inc. // Copyright (C) 1998-2000 by DooM Legacy Team. -// Copyright (C) 1999-2023 by Sonic Team Junior. +// Copyright (C) 1999-2024 by Sonic Team Junior. // // This program is free software distributed under the // terms of the GNU General Public License, version 2. @@ -21,8 +21,9 @@ #include "m_random.h" #include "lua_script.h" #include "lua_hook.h" +#include "acs/interface.h" #include "m_perfstats.h" -#include "i_system.h" // I_GetPreciseTime +#include "i_system.h" #include "r_main.h" #include "r_fps.h" #include "i_video.h" // rendermode @@ -38,6 +39,8 @@ tic_t leveltime; +UINT32 thinker_era = 0; + // // THINKERS // All thinkers should be allocated by Z_Calloc @@ -200,10 +203,22 @@ void Command_CountMobjs_f(void) void P_InitThinkers(void) { UINT8 i; + + P_InvalidateThinkersWithoutInit(); + for (i = 0; i < NUM_THINKERLISTS; i++) thlist[i].prev = thlist[i].next = &thlist[i]; } +// +// P_InvalidateThinkersWithoutInit +// + +void P_InvalidateThinkersWithoutInit(void) +{ + thinker_era++; +} + // Adds a new thinker at the end of the list. void P_AddThinker(const thinklistnum_t n, thinker_t *thinker) { @@ -443,6 +458,9 @@ static inline void P_RunThinkers(void) PS_STOP_TIMING(ps_thlist_times[i]); } + PS_START_TIMING(ps_acs_time); + ACS_Tick(); + PS_STOP_TIMING(ps_acs_time); } // diff --git a/src/p_tick.h b/src/p_tick.h index bbc227e081433652a9a3d79b8ab0513c531677bc..93e2249ba01f90ee14e6100349baf3bf6f75ac1b 100644 --- a/src/p_tick.h +++ b/src/p_tick.h @@ -2,7 +2,7 @@ //----------------------------------------------------------------------------- // Copyright (C) 1993-1996 by id Software, Inc. // Copyright (C) 1998-2000 by DooM Legacy Team. -// Copyright (C) 1999-2023 by Sonic Team Junior. +// Copyright (C) 1999-2024 by Sonic Team Junior. // // This program is free software distributed under the // terms of the GNU General Public License, version 2. @@ -16,8 +16,8 @@ #include "doomdef.h" -#ifdef __GNUG__ -#pragma interface +#ifdef __cplusplus +extern "C" { #endif extern tic_t leveltime; @@ -31,6 +31,8 @@ void P_PreTicker(INT32 frames); void P_DoTeamscrambling(void); void P_RemoveThinkerDelayed(thinker_t *thinker); //killed +extern UINT32 thinker_era; + mobj_t *P_SetTarget2(mobj_t **mo, mobj_t *target #ifdef PARANOIA , const char *source_file, int source_line @@ -43,4 +45,8 @@ mobj_t *P_SetTarget2(mobj_t **mo, mobj_t *target #define P_SetTarget P_SetTarget2 #endif +#ifdef __cplusplus +} // extern "C" +#endif + #endif diff --git a/src/p_user.c b/src/p_user.c index 7cd128cf080792f64df4a56ca4aeb4e9076e6b94..949f3f24453d75c0ea10e79358ffca4b6d74078a 100644 --- a/src/p_user.c +++ b/src/p_user.c @@ -2,7 +2,7 @@ //----------------------------------------------------------------------------- // Copyright (C) 1993-1996 by id Software, Inc. // Copyright (C) 1998-2000 by DooM Legacy Team. -// Copyright (C) 1999-2023 by Sonic Team Junior. +// Copyright (C) 1999-2024 by Sonic Team Junior. // // This program is free software distributed under the // terms of the GNU General Public License, version 2. @@ -42,6 +42,7 @@ #include "st_stuff.h" #include "lua_script.h" #include "lua_hook.h" +#include "acs/interface.h" #include "b_bot.h" // Objectplace #include "m_cheat.h" @@ -968,6 +969,52 @@ pflags_t P_GetJumpFlags(player_t *player) return PF_JUMPED; } +UINT8 P_FindLowestLap(void) +{ + INT32 i; + UINT8 lowest = UINT8_MAX; + + if (!circuitmap) + return 0; + + for (i = 0; i < MAXPLAYERS; i++) + { + if (!playeringame[i] || players[i].spectator) + continue; + + if (lowest == UINT8_MAX || players[i].laps < lowest) + { + lowest = players[i].laps; + } + } + + CONS_Debug(DBG_GAMELOGIC, "Lowest laps found: %d\n", lowest); + + return lowest; +} + +UINT8 P_FindHighestLap(void) +{ + INT32 i; + UINT8 highest = 0; + + if (!circuitmap) + return 0; + + for (i = 0; i < MAXPLAYERS; i++) + { + if (!playeringame[i] || players[i].spectator) + continue; + + if (players[i].laps > highest) + highest = players[i].laps; + } + + CONS_Debug(DBG_GAMELOGIC, "Highest laps found: %d\n", highest); + + return highest; +} + // // P_PlayerInPain // @@ -2316,6 +2363,7 @@ void P_DoPlayerExit(player_t *player, boolean finishedflag) player->powers[pw_underwater] = 0; player->powers[pw_spacetime] = 0; P_RestoreMusic(player); + ACS_RunPlayerFinishScript(player); } boolean P_InSpaceSector(mobj_t *mo) // Returns true if you are in space diff --git a/src/r_defs.h b/src/r_defs.h index da4dd2d70e6049479eacd24c51af11b4a995507b..9fc4527e1be0a228aae33df54345d8f7ceeda82a 100644 --- a/src/r_defs.h +++ b/src/r_defs.h @@ -2,7 +2,7 @@ //----------------------------------------------------------------------------- // Copyright (C) 1993-1996 by id Software, Inc. // Copyright (C) 1998-2000 by DooM Legacy Team. -// Copyright (C) 1999-2023 by Sonic Team Junior. +// Copyright (C) 1999-2024 by Sonic Team Junior. // // This program is free software distributed under the // terms of the GNU General Public License, version 2. @@ -409,6 +409,48 @@ typedef enum SSF_NOPHYSICSCEILING = 1<<22, } sectorspecialflags_t; +typedef enum +{ + // Mask to get trigger type. + SECSPAC_TRIGGERMASK = 0x0000000F, + + // Special action is activated once. + SECSPAC_ONCESPECIAL = 0x00000000, + + // Special action is repeatable. + SECSPAC_REPEATSPECIAL = 0x00000001, + + // Special action is activated continously. + SECSPAC_CONTINUOUSSPECIAL = 0x00000002, + + // When a player enters this sector. + SECSPAC_ENTER = 0x00000010, + + // When a player touches the floor of this sector. + SECSPAC_FLOOR = 0x00000020, + + // When a player touches the ceiling of this sector. + SECSPAC_CEILING = 0x00000040, + + // When an enemy enters this sector. + SECSPAC_ENTERMONSTER = 0x00000080, + + // When an enemy touches the floor of this sector. + SECSPAC_FLOORMONSTER = 0x00000100, + + // When an enemy touches the ceiling of this sector. + SECSPAC_CEILINGMONSTER = 0x00000200, + + // When a projectile enters this sector. + SECSPAC_ENTERMISSILE = 0x00000400, + + // When a projectile touches the floor of this sector. + SECSPAC_FLOORMISSILE = 0x00000800, + + // When a projectile touches the ceiling of this sector. + SECSPAC_CEILINGMISSILE = 0x00001000, +} sectoractionflags_t; + typedef enum { SD_NONE = 0, @@ -552,6 +594,12 @@ typedef struct sector_s // portals UINT32 portal_floor; UINT32 portal_ceiling; + + // Action specials + INT16 action; + INT32 args[NUM_SCRIPT_ARGS]; + char *stringargs[NUM_SCRIPT_STRINGARGS]; + sectoractionflags_t activation; } sector_t; // @@ -569,9 +617,6 @@ typedef enum #define SPECIAL_SECTOR_SETPORTAL 6 -#define NUMLINEARGS 10 -#define NUMLINESTRINGARGS 2 - #define NO_SIDEDEF 0xFFFFFFFF typedef struct line_s @@ -585,10 +630,11 @@ typedef struct line_s // Animation related. INT16 flags; + UINT32 activation; INT16 special; taglist_t tags; - INT32 args[NUMLINEARGS]; - char *stringargs[NUMLINESTRINGARGS]; + INT32 args[NUM_SCRIPT_ARGS]; + char *stringargs[NUM_SCRIPT_STRINGARGS]; // Visual appearance: sidedefs. UINT32 sidenum[2]; // sidenum[1] will be NO_SIDEDEF if one-sided @@ -633,6 +679,9 @@ typedef struct // We do not maintain names here. INT32 toptexture, bottomtexture, midtexture; + // Interpolator installed? (R_CreateInterpolator_SideScroll) + boolean acs_interpolated; + // Linedef the sidedef belongs to line_t *line; @@ -900,7 +949,7 @@ typedef struct #endif // Possible alpha types for a patch. -enum patchalphastyle {AST_COPY, AST_TRANSLUCENT, AST_ADD, AST_SUBTRACT, AST_REVERSESUBTRACT, AST_MODULATE, AST_OVERLAY, AST_FOG}; +typedef enum patchalphastyle { AST_COPY, AST_TRANSLUCENT, AST_ADD, AST_SUBTRACT, AST_REVERSESUBTRACT, AST_MODULATE, AST_OVERLAY, AST_FOG } patchalphastyle_t; typedef enum { diff --git a/src/r_fps.h b/src/r_fps.h index cd40b0a9a572b23b97bb9b2cc493ea6a34a243a7..90677c1d24e8b1ff6d6c84d1aea4a06498d47528 100644 --- a/src/r_fps.h +++ b/src/r_fps.h @@ -3,7 +3,7 @@ // Copyright (C) 1993-1996 by id Software, Inc. // Copyright (C) 1998-2000 by DooM Legacy Team. // Copyright (C) 1999-2000 by Jess Haas, Nicolas Kalkhof, Colin Phipps, Florian Schulze, Andrey Budko (prboom) -// Copyright (C) 1999-2019 by Sonic Team Junior. +// Copyright (C) 1999-2024 by Sonic Team Junior. // // This program is free software distributed under the // terms of the GNU General Public License, version 2. @@ -20,6 +20,10 @@ #include "r_state.h" #include "m_perfstats.h" // ps_metric_t +#ifdef __cplusplus +extern "C" { +#endif + extern consvar_t cv_fpscap; extern ps_metric_t ps_interp_frac; @@ -165,4 +169,8 @@ void R_UpdateMobjInterpolators(void); void R_ResetMobjInterpolationState(mobj_t *mobj); void R_ResetPrecipitationMobjInterpolationState(precipmobj_t *mobj); +#ifdef __cplusplus +} // extern "C" +#endif + #endif diff --git a/src/r_skins.h b/src/r_skins.h index 1f2c57472d23ffd3026f0370e591dcd18ad77193..9deec2453de5284ba971137a69f6611e20df9723 100644 --- a/src/r_skins.h +++ b/src/r_skins.h @@ -2,7 +2,7 @@ //----------------------------------------------------------------------------- // Copyright (C) 1993-1996 by id Software, Inc. // Copyright (C) 1998-2000 by DooM Legacy Team. -// Copyright (C) 1999-2023 by Sonic Team Junior. +// Copyright (C) 1999-2024 by Sonic Team Junior. // // This program is free software distributed under the // terms of the GNU General Public License, version 2. @@ -21,6 +21,10 @@ #include "r_picformats.h" // spriteinfo_t #include "r_defs.h" // spritedef_t +#ifdef __cplusplus +extern "C" { +#endif + /// Defaults #define SKINNAMESIZE 16 // should be all lowercase!! S_SKIN processing does a strlwr @@ -118,4 +122,8 @@ boolean P_IsStateSprite2Super(state_t *state); void R_RefreshSprite2(void); +#ifdef __cplusplus +} // extern "C" +#endif + #endif //__R_SKINS__ diff --git a/src/r_textures.h b/src/r_textures.h index eb68ec09f21d4fe8d847b048ac12ba9bd3a1817d..e42f02cd0fcc7c95ae8d89a7529688ab6f163a0f 100644 --- a/src/r_textures.h +++ b/src/r_textures.h @@ -2,7 +2,7 @@ //----------------------------------------------------------------------------- // Copyright (C) 1993-1996 by id Software, Inc. // Copyright (C) 1998-2000 by DooM Legacy Team. -// Copyright (C) 1999-2023 by Sonic Team Junior. +// Copyright (C) 1999-2024 by Sonic Team Junior. // // This program is free software distributed under the // terms of the GNU General Public License, version 2. @@ -19,6 +19,10 @@ #include "p_setup.h" // levelflats #include "r_data.h" +#ifdef __cplusplus +extern "C" { +#endif + #ifdef __GNUG__ #pragma interface #endif @@ -108,4 +112,8 @@ const char *R_TextureNameForNum(INT32 num); extern INT32 numtextures; +#ifdef __cplusplus +} // extern "C" +#endif + #endif diff --git a/src/s_sound.h b/src/s_sound.h index d64778fc37db6fffdf8027ad7aa3cb4f338a6a33..8b7b80fa2dd11c636bf8abb87d76e85abc22e4dd 100644 --- a/src/s_sound.h +++ b/src/s_sound.h @@ -2,7 +2,7 @@ //----------------------------------------------------------------------------- // Copyright (C) 1993-1996 by id Software, Inc. // Copyright (C) 1998-2000 by DooM Legacy Team. -// Copyright (C) 1999-2023 by Sonic Team Junior. +// Copyright (C) 1999-2024 by Sonic Team Junior. // // This program is free software distributed under the // terms of the GNU General Public License, version 2. @@ -25,6 +25,10 @@ extern openmpt_module *openmpt_mhandle; #endif +#ifdef __cplusplus +extern "C" { +#endif + // mask used to indicate sound origin is player item pickup #define PICKUP_SOUND 0x8000 @@ -329,4 +333,8 @@ void S_StopSoundByNum(sfxenum_t sfxnum); #define S_StartScreamSound S_StartSound #endif +#ifdef __cplusplus +} // extern "C" +#endif + #endif diff --git a/src/sdl/CMakeLists.txt b/src/sdl/CMakeLists.txt index ee48fa2b154568889c50d4e0a6c8d97c5c2a7bf0..99425108e69b3761fd03de7790d02232e44bb898 100644 --- a/src/sdl/CMakeLists.txt +++ b/src/sdl/CMakeLists.txt @@ -33,8 +33,6 @@ target_compile_options(SRB2SDL2 PRIVATE -Wall -Wno-trigraphs -W # Was controlled by RELAXWARNINGS - -pedantic - -Wpedantic -Wfloat-equal -Wundef -Wpointer-arith diff --git a/src/tables.h b/src/tables.h index 65d7f72433177f77d711c5e4b5809ed2f9d6e2b8..7a7a9300f50cd908b79e880a1629e5ad63b056ec 100644 --- a/src/tables.h +++ b/src/tables.h @@ -2,7 +2,7 @@ //----------------------------------------------------------------------------- // Copyright (C) 1993-1996 by id Software, Inc. // Copyright (C) 1998-2000 by DooM Legacy Team. -// Copyright (C) 1999-2023 by Sonic Team Junior. +// Copyright (C) 1999-2024 by Sonic Team Junior. // // This program is free software distributed under the // terms of the GNU General Public License, version 2. @@ -14,6 +14,10 @@ #ifndef __TABLES__ #define __TABLES__ +#ifdef __cplusplus +extern "C" { +#endif + #ifdef LINUX #include <math.h> #endif @@ -120,4 +124,8 @@ matrix_t *FM_RotateZ(matrix_t *dest, angle_t rad); #define FINECOSINE(n) (finecosine[n]>>(FINE_FRACBITS-FRACBITS)) #define FINETANGENT(n) (finetangent[n]>>(FINE_FRACBITS-FRACBITS)) +#ifdef __cplusplus +} // extern "C" +#endif + #endif diff --git a/src/taglist.h b/src/taglist.h index d42a48f05ae056893c34180512901d1300e712e2..7c03549800ee64199495c41c3a953f8898f4f745 100644 --- a/src/taglist.h +++ b/src/taglist.h @@ -1,7 +1,7 @@ // SONIC ROBO BLAST 2 //----------------------------------------------------------------------------- // Copyright (C) 1998-2000 by DooM Legacy Team. -// Copyright (C) 1999-2023 by Sonic Team Junior. +// Copyright (C) 1999-2024 by Sonic Team Junior. // Copyright (C) 2020-2023 by Nev3r. // // This program is free software distributed under the @@ -11,11 +11,15 @@ /// \file taglist.h /// \brief Tag iteration and reading functions and macros' declarations. -#ifndef __R_TAGLIST__ -#define __R_TAGLIST__ +#ifndef __TAGLIST_H__ +#define __TAGLIST_H__ #include "doomtype.h" +#ifdef __cplusplus +extern "C" { +#endif + typedef INT16 mtag_t; #define MAXTAGS UINT16_MAX #define MTAG_GLOBAL -1 @@ -127,4 +131,8 @@ Notes: If no elements are found for a given tag, the loop inside won't be executed. */ -#endif //__R_TAGLIST__ +#ifdef __cplusplus +} // extern "C" +#endif + +#endif // __TAGLIST_H__ diff --git a/src/tcb_span.hpp b/src/tcb_span.hpp new file mode 100644 index 0000000000000000000000000000000000000000..fdc3a988a4c2ec5322deee7dc8fc3955f2ccb995 --- /dev/null +++ b/src/tcb_span.hpp @@ -0,0 +1,618 @@ + +/* +This is an implementation of C++20's std::span +http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2019/n4820.pdf +*/ + +// Copyright Tristan Brindle 2018. +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file ../../LICENSE_1_0.txt or copy at +// https://www.boost.org/LICENSE_1_0.txt) + +#ifndef TCB_SPAN_HPP_INCLUDED +#define TCB_SPAN_HPP_INCLUDED + +#include <array> +#include <cstddef> +#include <cstdint> +#include <type_traits> + +#ifndef TCB_SPAN_NO_EXCEPTIONS +// Attempt to discover whether we're being compiled with exception support +#if !(defined(__cpp_exceptions) || defined(__EXCEPTIONS) || defined(_CPPUNWIND)) +#define TCB_SPAN_NO_EXCEPTIONS +#endif +#endif + +#ifndef TCB_SPAN_NO_EXCEPTIONS +#include <cstdio> +#include <stdexcept> +#endif + +// Various feature test macros + +#ifndef TCB_SPAN_NAMESPACE_NAME +#define TCB_SPAN_NAMESPACE_NAME tcb +#endif + +#if __cplusplus >= 201703L || (defined(_MSVC_LANG) && _MSVC_LANG >= 201703L) +#define TCB_SPAN_HAVE_CPP17 +#endif + +#if __cplusplus >= 201402L || (defined(_MSVC_LANG) && _MSVC_LANG >= 201402L) +#define TCB_SPAN_HAVE_CPP14 +#endif + +namespace TCB_SPAN_NAMESPACE_NAME { + +// Establish default contract checking behavior +#if !defined(TCB_SPAN_THROW_ON_CONTRACT_VIOLATION) && \ + !defined(TCB_SPAN_TERMINATE_ON_CONTRACT_VIOLATION) && \ + !defined(TCB_SPAN_NO_CONTRACT_CHECKING) +#if defined(NDEBUG) || !defined(TCB_SPAN_HAVE_CPP14) +#define TCB_SPAN_NO_CONTRACT_CHECKING +#else +#define TCB_SPAN_TERMINATE_ON_CONTRACT_VIOLATION +#endif +#endif + +#if defined(TCB_SPAN_THROW_ON_CONTRACT_VIOLATION) +struct contract_violation_error : std::logic_error { + explicit contract_violation_error(const char* msg) : std::logic_error(msg) + {} +}; + +inline void contract_violation(const char* msg) +{ + throw contract_violation_error(msg); +} + +#elif defined(TCB_SPAN_TERMINATE_ON_CONTRACT_VIOLATION) +[[noreturn]] inline void contract_violation(const char* /*unused*/) +{ + std::terminate(); +} +#endif + +#if !defined(TCB_SPAN_NO_CONTRACT_CHECKING) +#define TCB_SPAN_STRINGIFY(cond) #cond +#define TCB_SPAN_EXPECT(cond) \ + cond ? (void) 0 : contract_violation("Expected " TCB_SPAN_STRINGIFY(cond)) +#else +#define TCB_SPAN_EXPECT(cond) +#endif + +#if defined(TCB_SPAN_HAVE_CPP17) || defined(__cpp_inline_variables) +#define TCB_SPAN_INLINE_VAR inline +#else +#define TCB_SPAN_INLINE_VAR +#endif + +#if defined(TCB_SPAN_HAVE_CPP14) || \ + (defined(__cpp_constexpr) && __cpp_constexpr >= 201304) +#define TCB_SPAN_HAVE_CPP14_CONSTEXPR +#endif + +#if defined(TCB_SPAN_HAVE_CPP14_CONSTEXPR) +#define TCB_SPAN_CONSTEXPR14 constexpr +#else +#define TCB_SPAN_CONSTEXPR14 +#endif + +#if defined(TCB_SPAN_HAVE_CPP14_CONSTEXPR) && \ + (!defined(_MSC_VER) || _MSC_VER > 1900) +#define TCB_SPAN_CONSTEXPR_ASSIGN constexpr +#else +#define TCB_SPAN_CONSTEXPR_ASSIGN +#endif + +#if defined(TCB_SPAN_NO_CONTRACT_CHECKING) +#define TCB_SPAN_CONSTEXPR11 constexpr +#else +#define TCB_SPAN_CONSTEXPR11 TCB_SPAN_CONSTEXPR14 +#endif + +#if defined(TCB_SPAN_HAVE_CPP17) || defined(__cpp_deduction_guides) +#define TCB_SPAN_HAVE_DEDUCTION_GUIDES +#endif + +#if defined(TCB_SPAN_HAVE_CPP17) || defined(__cpp_lib_byte) +#define TCB_SPAN_HAVE_STD_BYTE +#endif + +#if defined(TCB_SPAN_HAVE_CPP17) || defined(__cpp_lib_array_constexpr) +#define TCB_SPAN_HAVE_CONSTEXPR_STD_ARRAY_ETC +#endif + +#if defined(TCB_SPAN_HAVE_CONSTEXPR_STD_ARRAY_ETC) +#define TCB_SPAN_ARRAY_CONSTEXPR constexpr +#else +#define TCB_SPAN_ARRAY_CONSTEXPR +#endif + +#ifdef TCB_SPAN_HAVE_STD_BYTE +using byte = std::byte; +#else +using byte = unsigned char; +#endif + +#if defined(TCB_SPAN_HAVE_CPP17) +#define TCB_SPAN_NODISCARD [[nodiscard]] +#else +#define TCB_SPAN_NODISCARD +#endif + +TCB_SPAN_INLINE_VAR constexpr std::size_t dynamic_extent = SIZE_MAX; + +template <typename ElementType, std::size_t Extent = dynamic_extent> +class span; + +namespace detail { + +template <typename E, std::size_t S> +struct span_storage { + constexpr span_storage() noexcept = default; + + constexpr span_storage(E* p_ptr, std::size_t /*unused*/) noexcept + : ptr(p_ptr) + {} + + E* ptr = nullptr; + static constexpr std::size_t size = S; +}; + +template <typename E> +struct span_storage<E, dynamic_extent> { + constexpr span_storage() noexcept = default; + + constexpr span_storage(E* p_ptr, std::size_t p_size) noexcept + : ptr(p_ptr), size(p_size) + {} + + E* ptr = nullptr; + std::size_t size = 0; +}; + +// Reimplementation of C++17 std::size() and std::data() +#if defined(TCB_SPAN_HAVE_CPP17) || \ + defined(__cpp_lib_nonmember_container_access) +using std::data; +using std::size; +#else +template <class C> +constexpr auto size(const C& c) -> decltype(c.size()) +{ + return c.size(); +} + +template <class T, std::size_t N> +constexpr std::size_t size(const T (&)[N]) noexcept +{ + return N; +} + +template <class C> +constexpr auto data(C& c) -> decltype(c.data()) +{ + return c.data(); +} + +template <class C> +constexpr auto data(const C& c) -> decltype(c.data()) +{ + return c.data(); +} + +template <class T, std::size_t N> +constexpr T* data(T (&array)[N]) noexcept +{ + return array; +} + +template <class E> +constexpr const E* data(std::initializer_list<E> il) noexcept +{ + return il.begin(); +} +#endif // TCB_SPAN_HAVE_CPP17 + +#if defined(TCB_SPAN_HAVE_CPP17) || defined(__cpp_lib_void_t) +using std::void_t; +#else +template <typename...> +using void_t = void; +#endif + +template <typename T> +using uncvref_t = + typename std::remove_cv<typename std::remove_reference<T>::type>::type; + +template <typename> +struct is_span : std::false_type {}; + +template <typename T, std::size_t S> +struct is_span<span<T, S>> : std::true_type {}; + +template <typename> +struct is_std_array : std::false_type {}; + +template <typename T, std::size_t N> +struct is_std_array<std::array<T, N>> : std::true_type {}; + +template <typename, typename = void> +struct has_size_and_data : std::false_type {}; + +template <typename T> +struct has_size_and_data<T, void_t<decltype(detail::size(std::declval<T>())), + decltype(detail::data(std::declval<T>()))>> + : std::true_type {}; + +template <typename C, typename U = uncvref_t<C>> +struct is_container { + static constexpr bool value = + !is_span<U>::value && !is_std_array<U>::value && + !std::is_array<U>::value && has_size_and_data<C>::value; +}; + +template <typename T> +using remove_pointer_t = typename std::remove_pointer<T>::type; + +template <typename, typename, typename = void> +struct is_container_element_type_compatible : std::false_type {}; + +template <typename T, typename E> +struct is_container_element_type_compatible< + T, E, + typename std::enable_if< + !std::is_same< + typename std::remove_cv<decltype(detail::data(std::declval<T>()))>::type, + void>::value && + std::is_convertible< + remove_pointer_t<decltype(detail::data(std::declval<T>()))> (*)[], + E (*)[]>::value + >::type> + : std::true_type {}; + +template <typename, typename = size_t> +struct is_complete : std::false_type {}; + +template <typename T> +struct is_complete<T, decltype(sizeof(T))> : std::true_type {}; + +} // namespace detail + +template <typename ElementType, std::size_t Extent> +class span { + static_assert(std::is_object<ElementType>::value, + "A span's ElementType must be an object type (not a " + "reference type or void)"); + static_assert(detail::is_complete<ElementType>::value, + "A span's ElementType must be a complete type (not a forward " + "declaration)"); + static_assert(!std::is_abstract<ElementType>::value, + "A span's ElementType cannot be an abstract class type"); + + using storage_type = detail::span_storage<ElementType, Extent>; + +public: + // constants and types + using element_type = ElementType; + using value_type = typename std::remove_cv<ElementType>::type; + using size_type = std::size_t; + using difference_type = std::ptrdiff_t; + using pointer = element_type*; + using const_pointer = const element_type*; + using reference = element_type&; + using const_reference = const element_type&; + using iterator = pointer; + using reverse_iterator = std::reverse_iterator<iterator>; + + static constexpr size_type extent = Extent; + + // [span.cons], span constructors, copy, assignment, and destructor + template < + std::size_t E = Extent, + typename std::enable_if<(E == dynamic_extent || E <= 0), int>::type = 0> + constexpr span() noexcept + {} + + TCB_SPAN_CONSTEXPR11 span(pointer ptr, size_type count) + : storage_(ptr, count) + { + TCB_SPAN_EXPECT(extent == dynamic_extent || count == extent); + } + + TCB_SPAN_CONSTEXPR11 span(pointer first_elem, pointer last_elem) + : storage_(first_elem, last_elem - first_elem) + { + TCB_SPAN_EXPECT(extent == dynamic_extent || + last_elem - first_elem == + static_cast<std::ptrdiff_t>(extent)); + } + + template <std::size_t N, std::size_t E = Extent, + typename std::enable_if< + (E == dynamic_extent || N == E) && + detail::is_container_element_type_compatible< + element_type (&)[N], ElementType>::value, + int>::type = 0> + constexpr span(element_type (&arr)[N]) noexcept : storage_(arr, N) + {} + + template <typename T, std::size_t N, std::size_t E = Extent, + typename std::enable_if< + (E == dynamic_extent || N == E) && + detail::is_container_element_type_compatible< + std::array<T, N>&, ElementType>::value, + int>::type = 0> + TCB_SPAN_ARRAY_CONSTEXPR span(std::array<T, N>& arr) noexcept + : storage_(arr.data(), N) + {} + + template <typename T, std::size_t N, std::size_t E = Extent, + typename std::enable_if< + (E == dynamic_extent || N == E) && + detail::is_container_element_type_compatible< + const std::array<T, N>&, ElementType>::value, + int>::type = 0> + TCB_SPAN_ARRAY_CONSTEXPR span(const std::array<T, N>& arr) noexcept + : storage_(arr.data(), N) + {} + + template < + typename Container, std::size_t E = Extent, + typename std::enable_if< + E == dynamic_extent && detail::is_container<Container>::value && + detail::is_container_element_type_compatible< + Container&, ElementType>::value, + int>::type = 0> + constexpr span(Container& cont) + : storage_(detail::data(cont), detail::size(cont)) + {} + + template < + typename Container, std::size_t E = Extent, + typename std::enable_if< + E == dynamic_extent && detail::is_container<Container>::value && + detail::is_container_element_type_compatible< + const Container&, ElementType>::value, + int>::type = 0> + constexpr span(const Container& cont) + : storage_(detail::data(cont), detail::size(cont)) + {} + + constexpr span(const span& other) noexcept = default; + + template <typename OtherElementType, std::size_t OtherExtent, + typename std::enable_if< + (Extent == dynamic_extent || OtherExtent == dynamic_extent || + Extent == OtherExtent) && + std::is_convertible<OtherElementType (*)[], + ElementType (*)[]>::value, + int>::type = 0> + constexpr span(const span<OtherElementType, OtherExtent>& other) noexcept + : storage_(other.data(), other.size()) + {} + + ~span() noexcept = default; + + TCB_SPAN_CONSTEXPR_ASSIGN span& + operator=(const span& other) noexcept = default; + + // [span.sub], span subviews + template <std::size_t Count> + TCB_SPAN_CONSTEXPR11 span<element_type, Count> first() const + { + TCB_SPAN_EXPECT(Count <= size()); + return {data(), Count}; + } + + template <std::size_t Count> + TCB_SPAN_CONSTEXPR11 span<element_type, Count> last() const + { + TCB_SPAN_EXPECT(Count <= size()); + return {data() + (size() - Count), Count}; + } + + template <std::size_t Offset, std::size_t Count = dynamic_extent> + using subspan_return_t = + span<ElementType, Count != dynamic_extent + ? Count + : (Extent != dynamic_extent ? Extent - Offset + : dynamic_extent)>; + + template <std::size_t Offset, std::size_t Count = dynamic_extent> + TCB_SPAN_CONSTEXPR11 subspan_return_t<Offset, Count> subspan() const + { + TCB_SPAN_EXPECT(Offset <= size() && + (Count == dynamic_extent || Offset + Count <= size())); + return {data() + Offset, + Count != dynamic_extent ? Count : size() - Offset}; + } + + TCB_SPAN_CONSTEXPR11 span<element_type, dynamic_extent> + first(size_type count) const + { + TCB_SPAN_EXPECT(count <= size()); + return {data(), count}; + } + + TCB_SPAN_CONSTEXPR11 span<element_type, dynamic_extent> + last(size_type count) const + { + TCB_SPAN_EXPECT(count <= size()); + return {data() + (size() - count), count}; + } + + TCB_SPAN_CONSTEXPR11 span<element_type, dynamic_extent> + subspan(size_type offset, size_type count = dynamic_extent) const + { + TCB_SPAN_EXPECT(offset <= size() && + (count == dynamic_extent || offset + count <= size())); + return {data() + offset, + count == dynamic_extent ? size() - offset : count}; + } + + // [span.obs], span observers + constexpr size_type size() const noexcept { return storage_.size; } + + constexpr size_type size_bytes() const noexcept + { + return size() * sizeof(element_type); + } + + TCB_SPAN_NODISCARD constexpr bool empty() const noexcept + { + return size() == 0; + } + + // [span.elem], span element access + TCB_SPAN_CONSTEXPR11 reference operator[](size_type idx) const + { + TCB_SPAN_EXPECT(idx < size()); + return *(data() + idx); + } + + TCB_SPAN_CONSTEXPR11 reference front() const + { + TCB_SPAN_EXPECT(!empty()); + return *data(); + } + + TCB_SPAN_CONSTEXPR11 reference back() const + { + TCB_SPAN_EXPECT(!empty()); + return *(data() + (size() - 1)); + } + + constexpr pointer data() const noexcept { return storage_.ptr; } + + // [span.iterators], span iterator support + constexpr iterator begin() const noexcept { return data(); } + + constexpr iterator end() const noexcept { return data() + size(); } + + TCB_SPAN_ARRAY_CONSTEXPR reverse_iterator rbegin() const noexcept + { + return reverse_iterator(end()); + } + + TCB_SPAN_ARRAY_CONSTEXPR reverse_iterator rend() const noexcept + { + return reverse_iterator(begin()); + } + +private: + storage_type storage_{}; +}; + +#ifdef TCB_SPAN_HAVE_DEDUCTION_GUIDES + +/* Deduction Guides */ +template <class T, size_t N> +span(T (&)[N])->span<T, N>; + +template <class T, size_t N> +span(std::array<T, N>&)->span<T, N>; + +template <class T, size_t N> +span(const std::array<T, N>&)->span<const T, N>; + +template <class Container> +span(Container&)->span<typename std::remove_reference< + decltype(*detail::data(std::declval<Container&>()))>::type>; + +template <class Container> +span(const Container&)->span<const typename Container::value_type>; + +#endif // TCB_HAVE_DEDUCTION_GUIDES + +template <typename ElementType, std::size_t Extent> +constexpr span<ElementType, Extent> +make_span(span<ElementType, Extent> s) noexcept +{ + return s; +} + +template <typename T, std::size_t N> +constexpr span<T, N> make_span(T (&arr)[N]) noexcept +{ + return {arr}; +} + +template <typename T, std::size_t N> +TCB_SPAN_ARRAY_CONSTEXPR span<T, N> make_span(std::array<T, N>& arr) noexcept +{ + return {arr}; +} + +template <typename T, std::size_t N> +TCB_SPAN_ARRAY_CONSTEXPR span<const T, N> +make_span(const std::array<T, N>& arr) noexcept +{ + return {arr}; +} + +template <typename Container> +constexpr span<typename std::remove_reference< + decltype(*detail::data(std::declval<Container&>()))>::type> +make_span(Container& cont) +{ + return {cont}; +} + +template <typename Container> +constexpr span<const typename Container::value_type> +make_span(const Container& cont) +{ + return {cont}; +} + +template <typename ElementType, std::size_t Extent> +span<const byte, ((Extent == dynamic_extent) ? dynamic_extent + : sizeof(ElementType) * Extent)> +as_bytes(span<ElementType, Extent> s) noexcept +{ + return {reinterpret_cast<const byte*>(s.data()), s.size_bytes()}; +} + +template < + class ElementType, size_t Extent, + typename std::enable_if<!std::is_const<ElementType>::value, int>::type = 0> +span<byte, ((Extent == dynamic_extent) ? dynamic_extent + : sizeof(ElementType) * Extent)> +as_writable_bytes(span<ElementType, Extent> s) noexcept +{ + return {reinterpret_cast<byte*>(s.data()), s.size_bytes()}; +} + +template <std::size_t N, typename E, std::size_t S> +constexpr auto get(span<E, S> s) -> decltype(s[N]) +{ + return s[N]; +} + +} // namespace TCB_SPAN_NAMESPACE_NAME + +namespace std { + +template <typename ElementType, size_t Extent> +class tuple_size<TCB_SPAN_NAMESPACE_NAME::span<ElementType, Extent>> + : public integral_constant<size_t, Extent> {}; + +template <typename ElementType> +class tuple_size<TCB_SPAN_NAMESPACE_NAME::span< + ElementType, TCB_SPAN_NAMESPACE_NAME::dynamic_extent>>; // not defined + +template <size_t I, typename ElementType, size_t Extent> +class tuple_element<I, TCB_SPAN_NAMESPACE_NAME::span<ElementType, Extent>> { +public: + static_assert(Extent != TCB_SPAN_NAMESPACE_NAME::dynamic_extent && + I < Extent, + ""); + using type = ElementType; +}; + +} // end namespace std + +#endif // TCB_SPAN_HPP_INCLUDED diff --git a/src/w_wad.c b/src/w_wad.c index 78d26f9056c16e818d0384d1f91f2237d8b8024b..abbae94a69721ef1bab5fb9405f4e52086a74e0f 100644 --- a/src/w_wad.c +++ b/src/w_wad.c @@ -2,7 +2,7 @@ //----------------------------------------------------------------------------- // Copyright (C) 1993-1996 by id Software, Inc. // Copyright (C) 1998-2000 by DooM Legacy Team. -// Copyright (C) 1999-2023 by Sonic Team Junior. +// Copyright (C) 1999-2024 by Sonic Team Junior. // // This program is free software distributed under the // terms of the GNU General Public License, version 2. @@ -1677,6 +1677,44 @@ lumpnum_t W_CheckNumForNameInBlock(const char *name, const char *blockstart, con return LUMPERROR; } +// +// W_CheckNumForNameInFolder +// Checks only in PK3s in the specified folder +// +lumpnum_t W_CheckNumForNameInFolder(const char *lump, const char *folder) +{ + INT32 i; + lumpnum_t fsid, feid; + lumpnum_t check = INT16_MAX; + + // scan wad files backwards so patch lump files take precedence + for (i = numwadfiles - 1; i >= 0; i--) + { + if (wadfiles[i]->type == RET_PK3) + { + fsid = W_CheckNumForFolderStartPK3(folder, (UINT16)i, 0); + if (fsid == INT16_MAX) + { + continue; // Start doesn't exist? + } + + feid = W_CheckNumForFolderEndPK3(folder, (UINT16)i, fsid); + if (feid == INT16_MAX) + { + continue; // End doesn't exist? + } + + check = W_CheckNumForLongNamePwad(lump, (UINT16)i, fsid); + if (check < feid) + { + return (i<<16) + check; // found it, in our constraints + } + } + } + + return LUMPERROR; +} + // Used by Lua. Case sensitive lump checking, quickly... #include "fastcmp.h" UINT8 W_LumpExists(const char *name) @@ -2282,9 +2320,7 @@ void W_UnlockCachedPatch(void *patch) #ifdef HWRENDER if (rendermode == render_opengl) HWR_UnlockCachedPatch((GLPatch_t *)((patch_t *)patch)->hardware); - else #endif - Z_Unlock(patch); } void *W_CachePatchName(const char *name, INT32 tag) diff --git a/src/w_wad.h b/src/w_wad.h index 80e0e32fd585faaddcaf24dd8167e3f694d388f2..2358213f824d7da7856053bcb8e228da2f1b6fa1 100644 --- a/src/w_wad.h +++ b/src/w_wad.h @@ -2,7 +2,7 @@ //----------------------------------------------------------------------------- // Copyright (C) 1993-1996 by id Software, Inc. // Copyright (C) 1998-2000 by DooM Legacy Team. -// Copyright (C) 1999-2023 by Sonic Team Junior. +// Copyright (C) 1999-2024 by Sonic Team Junior. // // This program is free software distributed under the // terms of the GNU General Public License, version 2. @@ -18,8 +18,8 @@ #include "hardware/hw_data.h" #endif -#ifdef __GNUG__ -#pragma interface +#ifdef __cplusplus +extern "C" { #endif // a raw entry of the wad directory @@ -194,6 +194,7 @@ lumpnum_t W_CheckNumForLongName(const char *name); lumpnum_t W_GetNumForName(const char *name); // like W_CheckNumForName but I_Error on LUMPERROR lumpnum_t W_GetNumForLongName(const char *name); lumpnum_t W_CheckNumForNameInBlock(const char *name, const char *blockstart, const char *blockend); +lumpnum_t W_CheckNumForNameInFolder(const char *lump, const char *folder); UINT8 W_LumpExists(const char *name); // Lua uses this. size_t W_LumpLengthPwad(UINT16 wad, UINT16 lump); @@ -236,4 +237,8 @@ void W_VerifyFileMD5(UINT16 wadfilenum, const char *matchmd5); int W_VerifyNMUSlumps(const char *filename, boolean exit_on_error); +#ifdef __cplusplus +} // extern "C" +#endif + #endif // __W_WAD__ diff --git a/src/z_zone.h b/src/z_zone.h index ce7af4a159555e3a6c2be83e8d0eadcf8a7a69eb..4e333b4e1b88cbeb18b266e8d23ddc44c35d73ad 100644 --- a/src/z_zone.h +++ b/src/z_zone.h @@ -2,7 +2,7 @@ //----------------------------------------------------------------------------- // Copyright (C) 1993-1996 by id Software, Inc. // Copyright (C) 1998-2000 by DooM Legacy Team. -// Copyright (C) 1999-2023 by Sonic Team Junior. +// Copyright (C) 1999-2024 by Sonic Team Junior. // // This program is free software distributed under the // terms of the GNU General Public License, version 2. @@ -18,6 +18,10 @@ #include "doomdef.h" #include "doomtype.h" +#ifdef __cplusplus +extern "C" { +#endif + #ifdef __GNUC__ // __attribute__ ((X)) #if (__GNUC__ > 4) || (__GNUC__ == 4 && (__GNUC_MINOR__ >= 3 || (__GNUC_MINOR__ == 2 && __GNUC_PATCHLEVEL__ >= 5))) #define FUNCALLOC(X) __attribute__((alloc_size(X))) @@ -152,6 +156,9 @@ size_t Z_TagsUsage(INT32 lowtag, INT32 hightag); // Miscellaneous functions // char *Z_StrDup(const char *in); -#define Z_Unlock(p) (void)p // TODO: remove this now that NDS code has been removed + +#ifdef __cplusplus +} // extern "C" +#endif #endif