/*

 //***Che GLARING INACCURACIES IN THE WIKI THAT COST ME TONS OF TIME WASTED!
 

=============================================================================

Zandronum's DECORATE features:

- no anonymous functions.

- no Min(), Max(), no Abs().

- functions available: Sin(), Cos(), Sqrt(), CheckClass(), IsPointerEqual(), CallAcs() - that's all. But CheckClass() and IsPointerEqual() are *helluva powerful* if you use them right and CallAcs() is a killer feature allowing you use ACS in DECORATE expressions with immediate result - meaning you do complex math DECORATE sucks at using ACS. (CallAcs is an alias for ACS_NAmedExecuteWithResult, this alias only works in expressions -- you still have to write the full name if you use that as a function) meaning
		TNT1 A 0 A_JumpIf(CallAcs("AreImpsScared"), "Idle") // ok
		TNT1 A 0 CallAcs("ScareTheImps") // syntax error
		TNT1 A 0 ACS_NamedExecuteWithResult("ScareTheImps") // ok

- user variables: yes. With caveats that a) they are strictly integer while DECORATE operates on floats - you lose precision unless you employ tricks with multiplication on setting and division on using them. b) weapons can't use them because weapons run in the context of player pawn but the parser doesn't usderstand that. Must be declared with var int user_my_variable_name; (semicolon is obligatory) otherwise Zandronum stops at startup with an unknown variable syntax error. Must be set like this: TNT1 A 0 A_SetUserVar("user_my_variable_name", angle * 100.0) and can be freely used in DECORATE expressions: TNT1 A 0 A_SetAngle(user_my_variable_name / 100.0) 

- trinary operators: heck yeah! Including nested trinarry operators -- allows saving a lot on jumps . With a caveat they are numeric only: if you apply them to strings the parser would vomit bogus syntax error messages (instead of, say, just telling you trinary operators are not applicable to strings) and Zandronum won't start. Usage example: TNT1 A 0 A_SetAngle(user_a > user_b ? (user_a > 90 ? 90 : user_a) : user_b). Can be used to the same effect as of Min() and Max()

======== A_Jump and A_JumpIf ===============================================

They don't do anything on the multiplayer client unless the actor has CLIENTSIDEONLY flag

-- meaning execution continues.

So,
	TNT1 A 0 A_Jump(256, "...")
	Stop
and
	TNT1 A 0 A_JumpIf(true, "...")
	Stop
end in a disaster with monster disappearing clientside (been there, done that) but still running on the server - so you have an invisible monster now (this doesn't get corrected because there is nothing to sync).

A_JumpIfInventory - likewise, *except* if it is the local console player. E.g. it does work for the local console player. So this is the reason Mark-sensei used inventory tokens to control player vehicles.

The only way to switch states clientside is by using ACS scripts that do so - and that script must be launched serverside! Because of course ACS functions do nothing on the client as well.

==== A_SeekerMissile =======================================================

* does nothing clientside
* makes server send a full update to client (speeds, velocities, angle)

-- so, my attempts to transmit revenant fireball declumping to the clientside were a teensy bit superfluous.

============================================================================
 
Thing_Hate(hater_tid, hatee_tid, type)
 
	Type 0: only hate *one* specific thing (If TID is given, will hate the first thing with this TID the iterator finds,  but nothing more - doesn't change the hater's reaction to any other things with that TID)
	Types 1..6: if TID is given, sets the TIDtoHate actor variable that makes the hater hate *all* things with this TID and keep hunting them until there are no more. (see https://zdoom.org/wiki/DECORATE_expressions - it can be checked via JumpIf() but it is read-only.
		Also forces to forget current target and last enemy if their TID doesn't match -- i.e. it force-switches target and *interrupts states when called*. Calling this from an ACS script without a thorough checking is fraught with glitches up to ghost monsters forced from their Death state to their See state.
	Types 2,4,6: no sight check (super persistent predator)
	Types 1,2: ignore players unless attacked by them
	Types 3,4: aggro also at players that come into LOS
	Types 5,6: ignore players attacks, do not aggro

	Importantly, monsters that have the same TIDtoHate can *never* harm each other even if you make them infight because the DOHARMSPECIES flag is ignored in such case.
	
	Thus, you cannot make a free-for-all by making all monsters with a given TID hate that TID: they would aggro but their attacks would be harmless. You have to split them into two or three groups that hate each other.
	
	You *can* make a monster aggro at himself this way, if he is the only actor with the given TID, but that's useles because he would not be able to harm himself. He would simply be caught in an infinite loop of attacking.
  
=============================================================================

https://zdoom.org/wiki/A_SpawnItem does not mention the fact that if you pass zero as distance, it would use a sum of spawner and spawnee's radii. And here I thought why lots of A_SpawnItem() calls in BD code pass a distance of 1.
 

relevant Zandronum C++ source code:

	if (distance==0) 
	{
		// use the minimum distance that does not result in an overlap
		distance=(self->radius+GetDefaultByType(missile)->radius)>>FRACBITS;
	} 

=============================================================================

function A_JumpIfArmorType (supported by Zandronum) has a page on the wiki https://zdoom.org/wiki/A_JumpIfArmorType
but is MISSING from the wiki's page that lists the jump functions https://zdoom.org/wiki/Category:Decorate_Jump_functions

So you can only find it if you know about it... How exactly? :(

I found it by pure luck, while browsing internal data files in Zandronum sources!


Zandronum C++ source code:

	//
	// State jump function
	//

	DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_JumpIfArmorType)
	{
		ACTION_PARAM_START(3);
		ACTION_PARAM_NAME(Type, 0);
		ACTION_PARAM_STATE(JumpOffset, 1);
		ACTION_PARAM_INT(amount, 2);

		ACTION_SET_RESULT(false);    // Jumps should never set the result for inventory state chains!

		ABasicArmor * armor = (ABasicArmor *) self->FindInventory(NAME_BasicArmor);

		if (armor && armor->ArmorType == Type && armor->Amount >= amount)
			ACTION_JUMP(JumpOffset, 0);    // [BB] Clients know the player's inventory, so this is hopefully okay.
	}

GZDoom Zscript class Actor:

    //
    // rather pointless these days to do it this way.
    //

    bool CheckArmorType(name Type, int amount = 1)
    {
        let myarmor = BasicArmor(FindInventory("BasicArmor"));
        return myarmor != null && myarmor.ArmorType == type && myarmor.Amount >= amount;
    }

    action state A_JumpIfArmorType(name Type, statelabel label, int amount = 1)
    {
        return CheckArmorType(Type, amount)? ResolveState(label) : null;
    }
	
=============================================================================	
	
Both A_SpawnItem and A_SpawnItemEx *always* set the spawned actor's target pointer to the originator, 
*unless* the spawned actor has ISMONSTER flag or you pass SXF_TRANSFERPOINTERS to the function.


Where "originator" means the actor calling the function unless it is a missile -- in which case, will follow up the target pointer chain until hits an actor who is not a missile.

-- which *can* result in a NULL originator, especially if you use A_RearrangePointers (AAPTR_NULL,... to make the missile forget its progenitor.


=============================================================================	
	
Both A_SpawnItem and A_SpawnItemEx's (in relative mode) y and yvel are positive rightward and negative leftward

This is *opposite* of the map's coordinate system used by actor's y and momy (absolute values) where x points east and y points north so if you assume x is forward then y would be positive leftwards and negative rightwards!

This knowledge is vital for calculating any movements or offsets manually!

=============================================================================

Flags ( SXF_TRANSFERALPHA SXF_TRANSFERRENDERSTYLE SXF_SETTARGET SXF_SETTRACER SXF_NOPOINTERS SXF_ORIGINATOR SXF_TRANSFERSPRITEFRAME SXF_TRANSFERROLL SXF_ISTARGET SXF_ISMASTER SXF_ISTRACER)

are GZDoom-only.

Zandronum stops with a startup error if encounters any of them during parsing.

As a consequence, you cannot make a missile the spawned actor's target in Zandronum (because SXF_SETTARGET does not exist) nor can you change the calling actor's pointers to point to the spawned actor (because SXF_ISTARGET, SXF_ISMASTER, SXF_ISTRACER do not exist)

You cannot cheat your way around that via SXF_TRANSFERPOINTERS either because you cannot set any of the pointers to self in A_RearrangePointers.

============================================================================= 
 
https://zdoom.org/wiki/A_CustomMissile says: "CMF_TRACKOWNER — If a projectile is fired by another projectile, this flag can be used to ensure the secondary projectile knows who its real owner is. This behavior would have been the default, but it was not in the 2.0.x branch of ZDoom, allowing erroneous behavior which has been used by some mods, so fixing this would break compatibility."

This is misleading because the target seems to be tracked always when the calling actor is a missile.

The flag comes into play and becomes vital when you shoot missiles from a missile's death states. Because after a missile hits, it loses its MISSILE flag. So, without CMF_TRACKOWNER, the spawned child missile would
a) have a NULL target pointer - in fact, being ownerless so its damage would never be tracked back to the bastard who shoot the first missile
b) have set its tracer pointer set to the first missile's target (meaning a secondary a seeker missile would home on the monster who shoot the first missile, not on his target)

What CMF_TRACKOWNER does is making the IsMissile() check extra thorough, to also include the default flags as declared in DECORATE - thus a dead missile (or an actor that cleared its MISSILE flag manually) is still counted as a missile for all checks (including if the calling actor's target or tracer gets assigned to the spawned missile's tracer).

P.S. Sir Find in Files says I added that flag to 3252 function calls, thinking it necessary for my slaughterfest optimizations. That's helluvalot of wasted effort.  

===============================================================================

https://zdoom.org/wiki/A_Chase

CHF_NIGHTMAREFAST *only* has effects on frames with duration above 3 tics, and will *never* bring frame's duration below 3.
The formula for the frame duration on Nightmare is thus min(tics, max(3, tics - tics/2))
1->1, 2->2, 3->3, 4->3, 5->3, 6->3, 7->4, 8->4, 9->5, 10->5 ...

A_Chase auto-clears the target pointer if said target is dead or a friend (actor->IsFriend(actor->target))

A_Chase on friendly monsters auto-sets their target to their FriendPlayer->attacker if it is not friendly and is SHOOTABLE and has Health > 0

THEREFORE, uising A_ClearTarget to make friendly monster stop chasing the unreachable target and follow the player is *USELES* if you use A_Chase: he will re-aggro at the player's attacker in two calls!

===============================================================================

https://zdoom.org/wiki/Actor_flags#DOHARMSPECIES

The wiki fails to mention two vital facts:

1. this flag is ignored if the monsters are friends
2. this flag is ignored if the monsters are set to hate the same TID. For example, if you try to make a free for all by giving them all TID=1, setting DOHARMSPECIES on them and making them infight uising Thing_Hate(1,1,4) -- it won'tn't work: they would be unable to harm each other! Because they all hate the same thing: themselves.

=============================================================================== 
 
https://zdoom.org/wiki/A_JumpIfInventory and https://zdoom.org/wiki/A_JumpIfInTargetInventory 
 
The wiki fails to mention the fact that these functions do not work for fast projectiles.
The jump simply does not happen. 
Fast projectiles can modify their target's inventory just fine but they cannot check it.


===============================================================================

https://zdoom.org/wiki/A_FireCustomMissile

In Zandronum, the parameter listed in the wiki as "flags" is called "AimAtAngle" and is treated as a boolean value -- meaning if it is non-zero, the parameter Angle is added to the caller's angle, otherwise the parameter Angle is ignored. This is regardless of autoaim or seeker missile status.

Zandronum does not recognize the identifiers FPF_AIMATANGLE, FPF_TRANSFERTRANSLATION or FPF_NOAUTOAIM

===============================================================================

NODELAY doesn't work if you spawn the actor via A_SpawnItemEx from DECORATE instead of placing it there via map editor

The function won't be called (at least if it's an ACS script invocation).

You *have* to give the actor to be spawned that way at least 1 tic in its Spawn state before it could do anything.

===============================================================================

A_LookEx, when no tid to hate is set, no goal is set and LOF_NOSOUNDCHECK is passed, works as a dumb wrapper for P_LookForPlayers -- which, for friendly monsters when it is not DM and not a team game, works as a dumb wrapper for P_LookForEnemies -- which will use lastenemy if it does not find an enemy, clearing lastenemy in the process. So checking LOS afterwards *is* necessary. P_LookForEnemies calls P_BlockmapSearch with blockmap distance of 10 - so, presumably, the maximum distance the friendly can aggro at a monster is around 1280.

===============================================================================

"----" A 0 A_JumpIf(1 == CallACS("ScriptName",par1,par2,par3,par4), "Label")

is a thing.
as is

"----" A 0 ACS_NamedExecuteWithResult("ScriptName",par1,par2,par3,par4)

So you *can* execute ACS scripts immediately!

===============================================================================

SetActivatorToTarget(tid)

- only fails if cannot find actor tid

- if actor tid has no target, sets activator to actor tid and returns true

- SetActivatorToTarget(0) always returns true but does not change the activator if there is no target!

===============================================================================

SetActivator(tid, pointer)

- sets activator to NULL when fails - meaning your script runs ownerless after that point

- only sets activator to actor tid if pointer is AAPTR_DEFAULT

, so SetActivatorToTarget(tid) and SetActivator(tid, AAPTR_TARGET) behave radically differently when that target is null 

===============================================================================

SetPointer

- sets pointer to NULL ad returns false if pointer points to the activator

===============================================================================

A_CustomMissile

// if you pass *both* those flags it counts as passing NONE of them!
const int CMF_AIMOFFSET = 1;
const int CMF_AIMDIRECTION = 2;
CMF_AIMMODE = 3, // an internal game engine's constant, don't use in DECORATE

the code calculates aimmode as flags & CMF_AIMMODE - so it gets an integer (0..3) basically filtering out all flags except the two above.

the first check, if the target is NULL, the further execution is aborted unless aimmode equals 2. Meaning you *need* CMF_AIMDIRECTION and *no* CMF_AIMOFFSET to fire a missile anyway if there is no target!

aim modes 0 and 3 (meaning NO flags or BOTH flags): a full auto-aim that doesn't give a crap about your own angle. The missile's vector is finely adjusted to compensate for firing y and z offsets.
aim mode 1: a similar autoaim BUT with a caveat that the missile is fired at a vector parallel to the line connecting your origin with target's origin
aim mode 2: uses your angle, *force-sets the CMF_ABSOLUTEPITCH flag*. It's not "implies" as the wiki says, it just forces it on. 
	Bad news, there is *no* pitch auto-aiming in this mode, Do Pitch Yourself. Otherwise your missile is perfectly horizontal.

After that, if CMF_ABSOLUTEANGLE is in the flags, the angle you passed to the function overrides the missile's angle -- regardless of the aim mode! Meaning it discards the horizontal result of auto-aiming, but not the pitch. Otherwise it is added to missile's angle (your angle in aim mode 2, the result of autoaim calculation otherwise) as a delta.

Sooo, CMF_AIMDIRECTION | CMF_ABSOLUTEANGLE is a perfectly valid combination for shooting fireballs out of your ass regardless if you have a target.

Just don't forget to pass a pitch -- calculating it is best done via CallAcs() -- to do the targeting math.

You cannot know the missile's angle if autoaimed - you have to ask the missile (it can do that via ACS scripts that write into your user variables, but not immediately, only after it begins executing the next tic!) At first it seems that using A_FaceTarget + CMF_ABSOLUTEANGLE is a way around that, but... You lose autoaim corrections for horizontal firing offsets! So most monsters are fine, but BD's mancubus and cyberdemon are not. They'd keep missing! (been there, was caught in this pitfall)

CMF_SAVEPITCH makes it copy the pitch used for firing (including the autoaimed pitch you can't know) as the missile actor's pitch. Otherwise that will be zero! Only required if the missile performs some calculations or fires daughter missiles (like rocket backblast puffs in BD). Why is it zero by default? I bet, for backward compatibility with something. Missiles don't use their pitch for moving, they use velz. Changing their pitch mid-flight has absolutely no effect on their trajectory.
	*ALSO*, pitch is always zero in Zandronum's multiplayer client. It just does not get conveyed over the network. You need it for special effects? Transmit pitch yourself (via serverside ACS scripts calling clientside ACS scripts passing it as a parameter)

CMF_OFFSETPITCH: downwards is negative, upwards positive

In FBD, I use CMF_ABSOLUTEANGLE alone then pass actor's angle to the angle parameter to make the monster fire in the direction he is facing but still use vertical auto-aiming. I use this to fire past corners the player disappears behind, by simply jumping over A_FaceTarget if there is no LOS. The result is nasty, full on suppressive fire.

Also:
	- if CMF_ABSOLUTEPITCH is set, CMF_OFFSETPITCH does absolutely nothing.
	- CMF_TRACKOWNER is vital when you fire a daughter missile from missile's Death state because a missile that hit something stops counting as a missile and so daughter missile's pointers would be set incorrectly. CMF_TRACKOWNER is a cure for that, making the game engine also check if the spawning actor was initially a missile.
	- if a non-seeker missile fires a seeker missile, the daughter missile won't have a tracer set. Both must be seeker missiles for the daughter missile to inherit the spawner's tracer pointer.
	- if the spawning actor is not a missile, it becomes the spawned missile's target and its target becomes the spawned missile's tracer.
	- if the spawning actor is a missile (as said above, this check is influenced by the CMF_TRACKOWNER flag) then actors are iterated along the target pointers chain until stops at an actor that is not a missile or its target pointer is null. So, technically, the spawned missile's target pointer *may* point at a missile if that missile had no target. The target pointer *never* becomes null, unlike A_SpawnItemEx.
	- if the spawned missile has the SPECTRAL flag then missile's FriendPlayer property is set to either a correct player number, in case the spawning actor is a player, or to zero if it is not a player.
	
===============================================================================

ACS functions that do not exist in Zandronum (ignored silently)

	SetActorTeleFog
	SwapActorTeleFog
	SetActorRoll ---- Zandronum simply does not have a *concept* for roll. You cannot tilt player's view.
	ChangeActorRoll
	GetActorRoll
	QuakeEx	
	GetMaxInventory
	SetSectorDamage
	SetSectorTerrain
	SpawnParticle
	SetMusicVolume
	CheckProximity
	CheckActorState	
	CheckClass
	DamageActor ---- ARRRRGH, why oh why
	SetActorFlag ---- pain and suffering and using special inventory items that change actor's flag on being auto-used on pick up
	SetTranslation
	GetActorFloorTerrain
	StrArg
	Floor
	Round
	Ceil
	ScriptCall
	StartSlideshow
	GetSectorHealth
	GetLineHealth
	SetSubtitleNumber
	GetLineX
	GetLineY
	SetSectorGlow
	SetFogDensity

===============================================================================

Things that are NOT synchronized between server and client in Zandronum multiplayer:

1. Monster inventories (player's inventory being the sole exception)
2. User variables
3. The third argument of named clientside scripts you call from serverside scripts (is always zero)
4. Missile pitch (is always zero).

If you need to update something of these on the client, call a clientside ACS from a serverside ACS. You can pass maximum two values per call.

===============================================================================

ACS function NamedExecuteClientScript() is a thing

===============================================================================

Regarding ACS scripts' "NET" and "CLIENTSIDE" flags: what the wiki says is totally misleading!

Note OPEN CLIENTSIDE seems an exception -- BUT it does not count as "running from script" so Sector_SetFade and Sector_SetColor won't work from it. Although I confirm that Light_ChangeToValue does and light changes on each client work separately (I was making a sun fog visual effect, color and fade change depending on local player's view angle )

1. Scripts *without* these flags or with the "NET" only flag do not "execute on the server", they always execute *both* on the client and on the server (at least when called from DECORATE). It is your code's responsibility to tell those two apart, say, not to alter monster behavior on having no target clientside.

2. Scripts with the CLIENTSIDE flag are designed to be server commands transmitted to the client. When ran on the server, they add a command to be transmitted to the client to run that script! (Also, named scripts are limited to two arguments instead of three because the script name eats up one argument slot)
	Conclusion? Only run them from serverside scripts as means to convey certain state changes to the client. 

3. if ACS_NamedExecuteWithResult() (DECORATE alias CallAcs()) is called with a CLIENTSIDE script serverside it a) returns false as its result; b) adds add a server command to execute that script on the client!
	Meaning it tap-dances on the network bandwidth's grave!
	
4. The "NET" flag only comes to play during "puking", i.e. client trying to run a script on the server by transmitting a command to do so. Scripts without this flag get a "take a hike" treatment (to prevent cheats). So when do you need marking scrips as "NET"? Never, that's when. Unless you are a coding grandmaster much, much more knowledgeable than this ole ChebMaster.

// Do not use for optimization at home, kids

=== ACS delay() is not magic ==================================================

The script manager calls every running script every tic. If a script is currently executing delay(x), it simply decreases its cooldown variable by 1 and returns. But it is still gets called every tic consuming resources.

Non-issue normally but try making a script that runs on every monster monitoring his state (as my new headshot hitbox management scripts) and then try running Okuplok.

Whoops...?

=== How performance impactful is A_SetUserVar ? ===============================

Let's see...

DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_SetUserVar)
{
	ACTION_PARAM_START(2);
	ACTION_PARAM_NAME(varname, 0);
	ACTION_PARAM_INT(value, 1);	

	PSymbol *sym = self->GetClass()->Symbols.FindSymbol(varname, true);
	PSymbolVariable *var;

	if (sym == NULL || sym->SymbolType != SYM_Variable ||
		!(var = static_cast<PSymbolVariable *>(sym))->bUserVar ||
		var->ValueType.Type != VAL_Int)
	{
		Printf("%s is not a user variable in class %s\n", varname.GetChars(),
			self->GetClass()->TypeName.GetChars());
		return;
	}
	// Set the value of the specified user variable.
	*(int *)(reinterpret_cast<BYTE *>(self) + var->offset) = value;
}

GetClass() and the final = value are basic pointer dereferencing, zilch cost
FindSymbol is optimized searching through an ordered list, akin to sortin algorithms. 
So I'd say O * log (N) where N = 9 + number of constants in this class + number of user variables in this class. With comparison between integers (all resource strings are in fact integer indices), quite cheap but not free.
(where 9 is number of MBF action functions like MBF_Mushroom that are stored in the same symbols table)


================ A_Die ========================================================

- does NOTHING on the multiplyer client. Meaning client *fucking will* skip and merrily go executing further.
ALWAYS use it like this:
	"----" A 1 A_Die
	Wait

- otherwise (server or single player) calls P_DamageMobj with amount equal to current health and forced flag.

================ Vile raising in detail =======================================
   (a research for future mass-raising projectiles):

1. Only works from A_Chase with CHF_RESURRECT - so we need a raising actor who, most likely, should be an invisible intangible free-flying monster.

2. The resurrection check (P_CheckForResurrection) is called *before* the movement and cancels the movement on success.

2. The raiser has to be able to move - the raising check first looks if his movedir field is set to a valid direction and aborts if it is not. 

3. The check's origin is offset along x, y axes from the raiser's origin according his speed property and his movedir - thus, restricted to 8 fixed directions.

4. The check iterates over all actors in an infinitely tall (square) column with a radius of 32 at the check's origin. The conditions to count the corpse: 
	A) has a Raise state; 
	B) has the CORPSE flag; 
	C) the current state's duration is -1 -- meaning only truly static actors are raiseable. If it loops in a pseudo static state checking for something it will never get resurrected (case in point: BD corpses slumped against a wall, those keep checking in an endless loop if whatever they are propped against is gone and they should fall flat. They are *not* viable and there is no way around that); 
	D) is not a player; 
	E) x or y distance from the raiser's origin is no more than the sum of the corpse's default radius (taken from the blueprint instead of its current radius) and the raiser's radius; 
	F) is not separated from the raiser by a 3d floor (so, I suppose, the idea of viles walking across a glass ceiling above you is shot -- they WILL target you, see EDAY map 241, but they won't raise anything on your floor); 
	G) There is enough space for the resurrected monster, using blueprint's height and radius -- also prevents the vile from raising a monster inside himself thus paralyzing both. In my case, this is useles because corpse blueprint's hitbox is most likely than not very different from the monster's hitbox; 

5. The first corpse that passes all the checks triggers raising:
	A) The raiser is turned to face the corpse and put into his Heal state -- this is pure animation at this point, mind you. The process has started. Woe betide you if you forgot to give him a Heal state, the game engine then uses DEHACKED (stone age modding tech) compatibility fallbacks messing him up (the raiser becomes disguised as an archvile)
	B) If the raiser has the FRIENDLY flag then the raiser's target and lastenemy pointers are cleared if they point to the corpse
	C) The corpse is made to play the "vile/raise" sound
	D) If the corpse is in its Crush state, its translation (palette shift) is restored from its blueprint
	E) Corpse's width and height are restored from its blueprint (unless the ghost monsters bug is enabled and its height is below 2)
	F) Corpse's flags, damagetype and health are restored from its blueprint
	G) Corpse's target and lastenemy pointers are cleared
	H) If it counts as a kill, the map's monster count is increased
	I) Friendliness information is copied from the raiser, including the raiser's target -- and here we have a nice pitfall: if we spawn the raiser from a missile launched by the vile, we must *make sure* the raiser's target pointer is cleared first. Because otherwise the resurrected monster will attack the vile! (also, monsters raised this way would always spawn as dormant and hostile -- must make sure to disable such methods if the vile has the FRIENDLY flag) NOTE: the game engine does NOT check if the raisee is a monster!
	J) The former corpse is forced into his Raise state.

=== Client mode (Zandronum multiplayer client) =================================


*all* jump functions (with sole exception of local player checking his inventory)
and *all* ACS script start commands do exactly nothing if this returns true:

The only exceptions, only applying to the local player:
A_JumpIfInventory, ThrustThing, ThruztThingZ

bool NETWORK_InClientModeAndActorNotClientHandled( const AActor *pActor )
{
	return ( NETWORK_InClientMode() && ( NETWORK_IsActorClientHandled ( pActor ) == false ) );
}

bool NETWORK_IsActorClientHandled( const AActor *pActor )
{
	// [BB] Sanity check
	if ( pActor == NULL )
		return false;

	return ( ( pActor->NetworkFlags & NETFL_CLIENTSIDEONLY ) || ( pActor->NetID == -1 ) );
}

where NETFL_CLIENTSIDEONLY is assigned to actors spawned via A_CustomMissile, A_SpawnItem, A_SpawnItemEx when NETWORK_ShouldActorNotBeSpawned() returns false
- note, of these three only A_SpawnItemEx allows overriding the standard behavior and spawning *any* actor clientside if you pass SIXF_CLIENTSIDE flag to it!

BUT. That flag also guarantees the actor WONT be spawned serverside:

bool NETWORK_ShouldActorNotBeSpawned ( const AActor *pSpawner, const PClass *pSpawnType, const bool bForceClientSide )
{
	// [BB] Nothing to spawn.
	if ( pSpawnType == NULL )
		return true;

	bool bSpawnOnClient = ( bForceClientSide
	                        || ( pSpawner && ( pSpawner->NetworkFlags & NETFL_CLIENTSIDEONLY ) )
	                        || ( GetDefaultByType(pSpawnType)->NetworkFlags & NETFL_CLIENTSIDEONLY )
	                      );

	// [BB] Clients don't spawn non-client side only things.
	if ( NETWORK_InClientMode() && !bSpawnOnClient )
		return true;
	// [BB] The server doesn't spawn client side only things.
	else if ( ( NETWORK_GetState( ) == NETSTATE_SERVER ) && bSpawnOnClient )
		return true;
	else
		return false;
}

TL;DR only select few functions execute on the clientside properly for non-clientside actors. Including A_CheckSight (checks the local player only) and A_CheckFloor

ALSO, the game engine has a hard-coded upper limit to number of actors it cant sync via network. And that is 32767.


P.S. ulike what some wiki pages say, with NamedRequestScriptPuke() I don't see any code that would execute a NET CLIENTSIDE script on the client rather than the server! Meaning your NET CLIENTSIDE will run on the server, not on the client.




=== FFFFFfffffuuuuu---- (why my attempts at making skyboxes work in multiplayer failed) ========

case SVC2_SETDEFAULTSKYBOX:
	{
		int mobjNetID = pByteStream->ReadShort();
		if ( mobjNetID == -1  ) //***Che: fuckity fuck :(
			level.DefaultSkybox = NULL;
		else
		{
			AActor *mo = CLIENT_FindThingByNetID( mobjNetID );
			if ( mo && mo->GetClass()->IsDescendantOf( RUNTIME_CLASS( ASkyViewpoint ) ) )
				level.DefaultSkybox = static_cast<ASkyViewpoint *>( mo );
		}
	}
	break;
				

=== SetFogDensity does not exist ===============================================

Zandronum only supports light-based fog

=== Zanfronum doesn't know PRINTNAME_NEXTLEVEL =================================

Meaning you cannot get the next map's name in Zandronum.
Only three constants are defined,
	PRINTNAME_LEVELNAME
	PRINTNAME_LEVEL
	PRINTNAME_SKILL

=== https://zdoom.org/wiki/LineAttack_(ACS) ====================================

The last parameter listed there (pufftid) doesn't exist so the solution
provided @ https://zdoom.org/wiki/CheckSolidFooting cannot work.

=== UniqueTID() performance ====================================================

TIDs are stored in 128 separate pointer chains selected using the 7 least bits of a tid.

Every time you call UniqueTID(), or address an actor by its tid (which most ACS functions rely upon), a random number is chosen and then search performed along the matching pointer chain searching if that tid is used.

SO, it is a clear O(N). And my headboxes litter it *horribly*.

With sane amounts, sure. But trying manage 20000 headboxes on Okuplok leads to L2 cache rape. Even assuming a perfect implementation being just three 64-bit values, (the tid, the next pointer and the actor pointer), that's 480 kilobytes that get iterated over and over and over a bazillion times every tic. Note this is *more* than 256 Kilobytes L2 cache usually has. So... using unique TIDs @ Okuplok alone brings performance down to L3 cache speds.

A rework is absolutely necessary. Will use monster's tracer pointer to connect to the headbox and only fall back to unique TIDs for archviles and barons who rely on their tracer pointer for other purposes.
And, of cource, NO creating headboxes beforehand for every monster (if their count is above 2400, that's it) and NO running a management ACS script on each monster from spawn to death.
	

=== A_Chase(<state>, <state>, CHF_DONTMOVE)	fouls up actor's angle =========

Remember, no one told you that actot would not *turn*!
Unpleasant surprise, your stationary monster jerk around by turning arbitrarily.
Tough luck.

I personally hack around it like this:

	var int user_angle;
	...
	"----" A 0 A_SetUserVar("user_angle", angle * 100.0)
	"----" A 0 A_Chase("Missile", "Missile", CHF_DONTMOVE)
	"----" A 0 A_SetAngle("user_angle", angle * 0.01)
	


=== A_ChangeFlag("COUNTKILL",...) auto-updates monster count ================

    https://zdoom.org/w/index.php?title=Actor_flags states about COUNTKILL : "Changing this flag dynamically won't reduce the monster item count on the level. Use A_ChangeCountFlags if there's a need to modify this flag at runtime." 
	That is a lie. Zandronum automatically adjusts monster, item and secret counts when you call A_ChangeFlag


=== A_RadiusGive:  ===========================================================

Zandronum: 4 arguments - item, distance, flags, amount
	Does not return a value

GZDoom: 8 arguments - item, distance, flags, amount, class, species, minimum distance, limit for the cators to be given to (NOT actors iterated over, only those that pass all the checks count) - obviously you can't use the latter 4 because your mod won't run in Zandronum failing with a syntax error. 
	Returns the number of actors it has given the item to.
	
Both the sourceports implement this action function in their native C++ code.	

RGF_MISSILES: radically different between Zandronum and GZDoom. 

Zandronum uses regular iterator meaning missiles with the NOBLOCKMAP flag (by default ALL of them) will *never* get iterated! You basically need to modify all the missiles you need to radiusgive to with -NOBLOCKMAP for this to work!

	Also, you need RGF_OBJECTS if your missile is shootable or vulnerable (see Zandronum algo below)

GZDoom iterates over *all thinkers in the map* regardles of their position, accidentally totally disregarding if they have the NOBLOCKMAP flag (so by default *all* of the missiles get iterated over!) This is a huge problem for Brutal Doom since there is a bazilion of particles (blood, fire, smoke) that are all missiles and they are all invoked and checked for matching the flags dirtying the cache horribly and calculating distance for the distance check FOR EVERY PARTICLE ON THE MAP! So, all those particles that pass the distance check (and visibility check! What a waste!) receive the item in their inventories. Even if your item then checks the NOBLOCKMAP flag and aborts, the processing and cache dirtying cost cannot be avoided.

GZDoom supports extra flags RGF_INCLUSIVE, RGF_ITEMS, RGF_KILLED, RGF_EXFILTER, RGF_EXSPECIES, RGF_EITHER that allow, in theory, great control over what passes the filters -- but you cannot use them because your mod would not run in Zandronum.


The Zandronum algo is a chain of ifs, all must be true for the actor to pass:

	Using a single blockmap-based iterator (over all actors in an infinitely tall square column) based on the blockmap (any actors not included in the blockmap, including all missiles by default, aren't even touched)
	
	if the actor is an inventory item, unconditional fail. Zandronum can't radiusgive to items!
	
	RGF_SELF: if the actor is the activator and this flag is not passed, fail.
	
	RGF_NOTARGET, RGF_NOTRACER, RGF_NOMASTER: if the actor is in the activator's corresponding pointer, fail 
	
	RGF_CORPSES: if the actor has the flag +CORPSE and this flag is not passed, fail! Regardless of anything else

	if the corpse flag and RGF_CORPSES pass, the following will still fail:

	(and if the actor has no CORPSE flag and its health is below 1 or it has the KILLED flag, it will fail anyway and you cannot change that) - Zandronum can't radiusgive to killed!

	if the actor has ISMONSTER flag :
		if there is no RGF_MONSTERS, fail!
		the RGF_OBJECTS flag has no effect

	if the actor does *not* have ISMONSTER but has SHOOTABLE or VULNERABLE
		if there is no RGF_OBJECTS, fail
		
	if the actor does *not* have ISMONSTER, SHOOTABLE nor VULNERABLE flags but has the MISSILE flag:
		if there is no RGF_MISSILES flag, fail
		
	if RGF_CUBE
		check for coordinate deltas between activator's center and actor's center (using height / 2) are less than or equal the distance
		
	else
		check that the squared length of the vector connecting the activator's center and the actor's center (again, using height / 2) is less than or equal to the distance squared (calculated once outside the iteration loop)
		
	check that either RGF_NOSIGHT or the visibility check (ignore visibilty, ignore water boundary) passes
		
	All checks passed:
	
	create an instance of the item at map's origin
	
	if the item inherits from AHealth class, multiply its own amount by the amount argument otherwise set its amount to the amount argument
		
	set the item's DROPPED flag and clear counters
	
	try invoking item pickup by the actor
		if fails destroy the item
		if succeeds and the actor is a player and this is a server, transmit the inventory change to clients over the network
		
		
	
	Corollary: what should the flags be like for Brutal Doom, with its shootable corpses and some corpses marked as monsters?

		RGF_CORPSES | RGF_MONSTERS | RGF_OBJECTS	


The GZDoom algo is a more balanced tree of if-then-elses:

	The actual code is implemented as a separate function two different iterators can call:
	either iterating over *all* the thinkers (missiles) in the entire map for RGF_MISSILES
	or using a sophisticated portal-aware iterator.

	1.
		if the actor is the activator 
			if RGF_SELF, pass stage 1 check otherwise fail
	
		else if the actor has the MISSILE flag 
			if RGF_MISSILES, pass stage 1 check otherwise fail
	
		else if the actor is an inventory item class and RGF_ITEMS
			or actor has CORPSE and RGF_CORPSES
			or actor has KILLED and RGF_KILLED
			- pass stage 1 check
		
		else if (RGF_MONSTERS or RGF_OBJECTS or RGF_PLAYERS or RGF_VOODOO) and (actor's health is above 0)
			
			if the actor is a player and ((is actual player and RGF_PLAYERS) or (is a vodoo doll and RGF_VODOO) 
				- pass stage 1 check
					
			else if ((RGF_MONSTERS and (actor has ISMONSTER)) or
					(RGF_OBJECTS and ( not (actor has ISMONSTER)) and
					(actor has MF_SHOOTABLE or VULNERABLE))) 	
				- pass stage 1 check
	
	2.
		if stage 1 check wasn't passed, fail.
		
		Class and species checks I ignore here because not Zandronum-compatible and so of no interest
		
		RGF_NOTARGET, RGF_NOTRACER, RGF_NOMASTER checks (skipped if the actor is the activator) modified by
		RGF_INCLUSIVE - which I will ignore here because it is not Zandronum-compatible. Otherwise this check is identical to Zandronum's.
	
	3.
		calculate the vector from the activator to the actor's origin and correct its Z adding half actor's height
		if RGF_CUBE
			check for vecor's coordinate modulus(es?) to be less than or equal to the distance and greater than or equal to the minimum distance
			
		else
			calculate the squared length of the vector
			check that the squared length is less than or equal to the distance square (calculates with every iteration) and greather than or equal to minimum distance squared (calculates with every iteration)
			
		check that either RGF_NOSIGHT or the visibility check (ignore visibilty, ignore water boundary) passes
		
		create an instance of the item (no coordinates specified, presumably at map's origin) 
	
		if the item's kind is of health, multiply its own amount by the amount argument otherwise set its amount to the amount argument
		
		set the item's DROPPED flag and clear counters
		
		try invoking item pickup by the actor
			if fails destroy the item
			if succeeds return true which the iterator considers 1 and adds to the counter, to interrupt iterating if the limit is reached
			
Please note that these source ports do not use float(32-bit), they always calculate in double(64-bit) since float has vastly inferior precision at the map's edges to both the double and the ACS script's 16:16 fixed point (a 32-bit integer under the hood)		
	
*/