Author Topic: War3Source and War3Evo... Differences and possible upgrades to War3Source.  (Read 1095 times)

El Diablo

  • Hero Member
  • *****
  • Posts: 1126
    • View Profile
    • War3Evo
Every Year that comes around, I think about updating War3Source to my latest code / creation.  It is up to you guys if you want it.

War3Source and War3Evo... Differences and possible upgrades to War3Source.

** One of the major differences between the two is that War3Evo has been now using private forwards for races instead of global forwards.

Private forwards allows the War3Source Engine to send specific forward to races that are active in game.  If a race is not active in game, the forward will be removed from that race.

Here is parts code used in my Undead Scourge:

Code: [Select]
bool HooksLoaded = false;
public void Load_Hooks()
{
if(HooksLoaded) return;
HooksLoaded = true;

W3Hook(W3Hook_OnWar3Event, OnWar3Event);
W3Hook(W3Hook_OnWar3EventSpawn, OnWar3EventSpawn);
}
public void UnLoad_Hooks()
{
if(!HooksLoaded) return;
HooksLoaded = false;

W3UnhookAll(W3Hook_OnWar3Event);
W3UnhookAll(W3Hook_OnWar3EventSpawn);
}
bool RaceDisabled=true;
public void OnWar3RaceEnabled(newrace)
{
if(newrace==thisRaceID)
{
Load_Hooks();

RaceDisabled=false;
}
}
public void OnWar3RaceDisabled(oldrace)
{
if(oldrace==thisRaceID)
{
RaceDisabled=true;

UnLoad_Hooks();
}
}

Also take note that we use methodmaps in our War3Source code (ThisRacePlayer)
Code: [Select]
// Methodmap inherits W3player methodmap from war3source.inc
methodmap ThisRacePlayer < W3player
{
// constructor
public ThisRacePlayer(int playerindex) //constructor
{
if(!ValidPlayer(playerindex)) return view_as<ThisRacePlayer>(0);
return view_as<ThisRacePlayer>(playerindex); //make sure you do validity check on players
}
property bool canrevive
{
public get() { return Can_Player_Revive[this.index]; }
public set( bool value ) { Can_Player_Revive[this.index] =  value; }
}
property bool RESwarn
{
public get() { return RESwarn[this.index]; }
public set( bool value ) { RESwarn[this.index] =  value; }
}

public void hudmessage( char szMessage[MAX_MESSAGE_LENGTH], any ... )
{
char szBuffer[MAX_MESSAGE_LENGTH];
SetGlobalTransTarget(this.index);
VFormat(szBuffer, sizeof(szBuffer), szMessage, 3);
SetHudTextParams(-1.0, -1.0, 0.1, 255, 255, 0, 255);
ShowSyncHudText(this.index, ClientInfoMessage, szBuffer);
}
}

Code: [Select]
public void OnWar3Event(W3EVENT event,int client)
{
if(RaceDisabled)
return;

ThisRacePlayer player = ThisRacePlayer(client);

if(event==VampireImmunityCheckPre)
{
if(player && player.raceid==thisRaceID)
{
W3SetVar(EventArg1, Immunity_Skills);
W3SetVar(EventArg2, SKILL_LEECH);
return;
}
}
else if(event==OnVampireBuff)
{
if(player)
{
if(player.raceid==thisRaceID)
{
int skill_level=player.getskilllevel( thisRaceID, SKILL_LOWGRAV );
if(skill_level>0)
{
float CurrentCritChance = player.getbuff(fCritChance,thisRaceID);
//DP("Crit before %f",CurrentCritChance);
if(CurrentCritChance<1.0)
{
CurrentCritChance+=0.01 * skill_level;
player.setbuff(fCritChance,thisRaceID,CurrentCritChance,client);
player.setbuff(iCritMode,thisRaceID,1,client);
player.setbuff(fCritModifier,thisRaceID,CritDamageIncrease,client);
player.message("{blue}BloodLust increases crit chance by %f",CurrentCritChance);
}
else if(CurrentCritChance>1.0)
{
CurrentCritChance=1.0;
player.setbuff(fCritChance,thisRaceID,CurrentCritChance,client);
player.setbuff(iCritMode,thisRaceID,1,client);
player.setbuff(fCritModifier,thisRaceID,CritDamageIncrease,client);
}
//DP("Crit after %f",CurrentCritChance);
}
}
}
}
}

public void OnWar3EventSpawn (int client)
{
ThisRacePlayer player = ThisRacePlayer(client);
if(player.raceid==thisRaceID)
{
RemovePassiveSkills(client);
}
}


So yeah, another bonus is to start using methodmaps to make coding easier for race creation.   We've include that into our war3source.inc

You can also see we hook and unhook when a race is enabled or disabled.  This is added to each race so it can enable / disable private forwards to itself.


** Another improvement is putting the whole War3Source Engine into 1 source code.   Just the engine, not the addons or races.  War3source has a separate folder with all the parts of the engine labeled for function so that it is easier to find the source code you need to modify.   Adding everything into 1 source code also allows you to reuse resources throughout the code and to be able to call it internally without calling to every single native created by War3Source.


The only bad thing about this upgrade, is that any race created by you would have to be completely updated to the new code.   Since a majority of users of War3Source would have a very hard time doing that, I may just release this code separately.  It is mainly for TF2, but does have some code to support CS:S and CS:GO.   It does not have any support for Translations.   That would have to be added / worked on by someone else willing to go thru the headache with it.  I never removed "translations" functions from it completely, so that if I wanted to add it back later it would be much easier than a complete removal.


The other bad thing about upgrading War3Source without just releasing the code would be having to manually "convert" everything so that War3Source gains benefits from it.    I could make War3Source backwards compatible with private forwards included.   I would just add the option to allow you to create private forwards for your new races and give you time to convert your old races to it while still having all the global forwards functional.   Races with private forwards would allow you to save resources and helps reduce lag of the plugin.

Upgrades / Highlights:

* Private Forwards reduce plugin lag
* Creating War3Source into 1 source engine reduces plugin lag and resources
* Methodmaps makes race creation easier for everyone
* Improved bot management that creates bots with so much less lag on round start
* New ** Spell casting engine -- allows you to create spells with special graphics for your races that takes "casting time" in order to use a spell

Code: [Select]
enum W3SpellEffects
{
NoSpellEffects=0,
SpellEffectsPhysical,
SpellEffectsArcane,
SpellEffectsFire,
SpellEffectsFrost,
SpellEffectsNature,
SpellEffectsDarkness,
SpellEffectsLight,
}

enum W3SpellColor
{
NoSpellColor=0,
SpellColorYellow,
SpellColorGreen,
SpellColorBlue,
SpellColorRed,
}


/*
 * Returns if successful
 *
 * */

// if castingid = 0, then it will bring up a menu for the person wanting to cancel a spell.
native bool:War3_CancelSpell(client, target, castingid=0);

native bool:War3_CastSpell(client, target, W3SpellEffects:spelleffect, String:SpellColor[], raceid, SkillID, Float:duration);

/**
 * OnWar3StartCasting
 *
 * @param client client's index.
 * @param target client's target
 * @param spelleffect spell effect
 * @param SpellColor SpellColor = "r g b"
 * @param raceid race index
 * @param skillid skill index / also known as castingid
 * @return none
 */
forward OnWar3CastingStarted(client, target, W3SpellEffects:spelleffect, String:SpellColor[], raceid, skillid);

/**
 * OnWar3CastingStarted_Pre
 *
 * @param client client's index.
 * @param target client's target
 * @param spelleffect spell effect
 * @param SpellColor SpellColor = "r g b"
 * @param raceid race index
 * @param skillid skill index / also known as castingid
 * @param fcastingtime change the casting time here (float)
 * @return none
 */
forward Action OnWar3CastingStarted_Pre(int client, int target, W3SpellEffects spelleffect, char[] SpellColor, int raceid, int skillid, float &fcastingtime);

/**
 * OnWar3CastingFinished_Pre
 *
 * @param client client's index.
 * @param target client's target
 * @param spelleffect spell effect
 * @param SpellColor SpellColor = "r g b"
 * @param raceid race index
 * @param skillid skill index / also known as castingid
 * @return return Plugin_Handled or Plugin_Stop to prevent the spell from finshing, otherwise use Plugin_Continue
 */
forward Action:OnWar3CastingFinished_Pre(client, target, W3SpellEffects:spelleffect, String:SpellColor[], raceid, skillid);

/**
 * OnWar3EndCasting
 *
 * @param client client's index.
 * @param target client's target
 * @param spelleffect spell effect
 * @param SpellColor SpellColor = "r g b"
 * @param raceid race index
 * @param skillid skill index / also known as castingid
 * @return none
 */
forward OnWar3CastingFinished(client, target, W3SpellEffects:spelleffect, String:SpellColor[], raceid, skillid);


/**
 * OnWar3CancelSpell_Post
 *
 * @param client client's index. (The caster)
 * @param raceid client's raceid index. (The caster's raceid)
 * @param skillid client's skill index / also known as castingid (The Caster's Skill)
 * @param target client's target
 * @return none
 */
forward OnWar3CancelSpell_Post(client, raceid, skillid, target);

* If you so desire (I can add my shopmenu 3 option which allows you to create shop items that are permanent for players)
* New ** Teleport Engine for easy race creation teleport management:


Code: [Select]
/**
 * Teleports client to target location within given distance
 * or just teleports forward a certain distance.
 *
 * @param client
 * @param target
 * @param Float:ScaleVectorDistance - deals with target distance
 * @param Float:distance
 *
 * If you want to reset the cooldowns for your race / skill, then supply those
 * numbers in the correct params
 *
 * This resets cooldown if the teleport was unsuccessful due to boundaries
 * like walls being in the way.
 *
 * @param raceid *resets race/skill cooldown on fail
 * @param skillid *resets race/skill cooldown on fail
 *
 * @param return - no return
 *
 */
native W3Teleport(client,target=-1,Float:ScaleVectorDistance=-1.0,Float:distance=1200.0,raceid=-1,skillid=-1);

/**
 * Called right before GetAngleVectors Calculations.
 *
 * Currently this is the current Angle formula:
 *
 * GetClientEyeAngles(client,angle);
 *
 * If you want to use the target instead of the client and or
 * change the formula, you can do this here to change the angle
 * right before it calls GetAngleVectors.
 *
 * @param client
 * @param target
 * @param angle allows you to change the angle before the caluclations.
 *
*/
forward Action:OnW3TeleportGetAngleVectorsPre(client, target, Float:angle[3]);


/**
 * Called right before W3Teleport triggers its own TeleportEntity.
 *
 * All checks have been made, as far a distances and stuff in the way.
 * This is the time it will teleport the entity.
 *
 * We had used this to change the angle of the entity whom is teleported
 * and is the reason this Custom Action was created.
 * You can use it how you wish.
 *
 * If you would like to teleport the entity yourself, then return
 * Plugin_Handled after you teleport the entity
 * otherwise, W3Teleport will use this formula:
 * TeleportEntity(client,emptypos,NULL_VECTOR,NULL_VECTOR);
 * to teleport the entity.
 *
 * @param client
 * @param target
 *
 * @return Plugin_Handled if you plan to use this function to create
 * your own teleport entity function.
*/
forward Action:OnW3TeleportEntityCustom(client,target,Float:dir[3],Float:emptypos[3]);

/**
 * This is the location where the client will be teleporting to.
 *
 * Most races do immunity checking here.
 *
 * @return Plugin_Handled if this did NOT pass your checks and you will NOT allow the teleport to this location.
*/
forward Action:OnW3TeleportLocationChecking(client,Float:playerVec[3]);


/**
 * Called when there was a successful teleport
 *
 * @param client
 * @param target
 * @param raceid
 * @param skillid
 *
 * @param return - no return
 *
 */
forward OnW3Teleported(client,target,distance,raceid,skillid);

* CS:GO radar management for menu systems
* Updated Notification Engine


Code: [Select]
native War3_NotifyPlayerTookDamageFromSkill(victim, attacker, damage, skill);

native War3_NotifyPlayerTookDamageFromItem(victim, attacker, damage, item);

native War3_NotifyPlayerLeechedFromSkill(victim, attacker, health, skill);

native War3_NotifyPlayerLeechedFromItem(victim, attacker, health, item);

native War3_NotifyPlayerImmuneFromSkill(attacker, victim, skill);

native War3_NotifyPlayerImmuneFromItem(attacker, victim, skill);

native War3_NotifyPlayerSkillActivated(client,skill,bool:activated);

native War3_NotifyPlayerItemActivated(client,item,bool:activated);

* And numerous bug fixes that are so many to list here.


So yeah, so many updates and so little time.

How do you think I should release this code?   It completely changes War3source's current structure and races made for this would have to be optimized for it.  Or should I just release it as is, and people could just use what they need OR will you allow me to completely modify War3Source for this update so that War3Source would take the improvements and slowly migrate to the new system?


SO YEAH
UPGRADE OR NOT?
Do let me know
« Last Edit: March 20, 2016, 09:14:06 AM by El Diablo »

Ownz

  • Developer
  • Administrator
  • Hero Member
  • *****
  • Posts: 2437
  • chmod -R 777 *
    • View Profile
    • OwnageClan
how much slow incremental changes can these be split into?

i am skeptical of how private forwards improve performance and would ask for benchmarked numbers. I am not wiling to have that feature if there are no benchmarks.

I think translations is a deal beaker, and no backwards compatibility is a major concern

I do like the idea of methodmaps.

El Diablo

  • Hero Member
  • *****
  • Posts: 1126
    • View Profile
    • War3Evo
how much slow incremental changes can these be split into?

i am skeptical of how private forwards improve performance and would ask for benchmarked numbers. I am not wiling to have that feature if there are no benchmarks.

I think translations is a deal beaker, and no backwards compatibility is a major concern

I do like the idea of methodmaps.

as far as the https://wiki.alliedmods.net/SourceMod_Profiler , it seems to do it via

"
    Native calls from plugins.
    Callbacks into plugins.
    Internal function calls in plugins.
"

And that:

"
The following statistics are tracked for each item:

    Number calls.
    Total time spent in all calls.
    Minimum time spent in any call.
    Maximum time spent in any call.
"

Since Private forwards will only call to addons that registered for the call and global calls all the plugins, I feel this would be a no brainer.

So.. I'll work on creating a sourcemod profile (which War3Source will probably have multiple plugins being profiled at once, while War3Evo's version only needs a single plugin) ..

For instance, lets say we track all of the OnW3 Stuff, it will require multiple plugins to be tracked at the same time while War3Evo's version only requires 1 plugin to be tracked.

We would focus only on "callbacks", and nothing else.  Because War3Evo uses private forwards, and War3source uses global forwards... I figure that War3Evo's stuff (depending on how many races are active) will only call a few other plugins... while War3Source will call all plugins that has the forward.

I will download the latest copy of War3Source as is.. and set it up for profiling.  I will also do the same to War3Evo's latest version.   I will then print results here.


The test will have it where there is only 2 races active.   My race vs a bot race, and then another with my race vs a friend's race.   I may go further and include a whole server into this test.

I'll get back with you guys with the results at a later date.





* We could make War3source have 3 options:  Backwards compatible - to have both private and global forwards,  Old War3Source - global forwards only, New War3Source - private forwards only.


* As far as translations, you can keep those.  No need to change those that is already working for War3Source.   The engine updates shouldn't need to touch those.


* As far as adding in method maps, that is very easy to add and doesn't really require much to modify as far as source code.  I could update the races to become sample races for creating races using the new method maps.  The method map system would be easiest to add, if you want to just add / update those first.
« Last Edit: March 22, 2016, 06:31:10 PM by El Diablo »

Ownz

  • Developer
  • Administrator
  • Hero Member
  • *****
  • Posts: 2437
  • chmod -R 777 *
    • View Profile
    • OwnageClan
I investigated the forwarding system in to c++ source code
https://github.com/alliedmodders/sourcemod/blob/master/core/logic/ForwardSys.cpp

Quote
void CForwardManager::OnPluginLoaded(IPlugin *plugin)
{
   /* Attach any globally managed forwards */
   for (ForwardIter iter(m_managed); !iter.done(); iter.next()) {
      CForward *fwd = (*iter);
      IPluginFunction *pFunc = plugin->GetBaseContext()->GetFunctionByName(fwd->GetForwardName());
      if (pFunc)
         fwd->AddFunction(pFunc);
   }
}

void CForwardManager::OnPluginUnloaded(IPlugin *plugin)
{
   for (ForwardIter iter(m_managed); !iter.done(); iter.next()) {
      CForward *fwd = (*iter);
      fwd->RemoveFunctionsOfPlugin(plugin);
   }

   for (ForwardIter iter(m_unmanaged); !iter.done(); iter.next()) {
      CForward *fwd = (*iter);
      fwd->RemoveFunctionsOfPlugin(plugin);
   }
}

What this is doing is when a plugin is loaded, the sourcemod forwarding manager checks to see if it has implemented a function called  "someForward", if it is, add that to the list of plugins to call

There is no such thing as calling a plugin if it does NOT implement the forward, if there are 300 plugins and 1 plugin that implements the forward, only 1 call is made.

Quote
int CForward::Execute(cell_t *result, IForwardFilter *filter)
{
.... redacted

   for (FuncIter iter(m_functions); !iter.done(); iter.next())
   {
      IPluginFunction *func = (*iter);
execute will call the list of functions that has been put on the list at OnPluginLoad

Private forwards only have the benefit of being able to remove itself and re-add itself to this list. But if you are disabling the the race by removing all of its forwards, might as well pause the whole plugin, that disables all forwards to that plugin which is managed by sourcemod itself

Quote
void CForwardManager::OnPluginPauseChange(IPlugin *plugin, bool paused)
{
   if (paused)
      return;

   /* Attach any globally managed forwards */
   for (ForwardIter iter(m_managed); !iter.done(); iter.next()) {
      CForward *fwd = (*iter);
      IPluginFunction *pFunc = plugin->GetBaseContext()->GetFunctionByName(fwd->GetForwardName());
      // Only add functions, if they aren't registered yet!
      if (pFunc && !fwd->IsFunctionRegistered(pFunc))
      {
         fwd->AddFunction(pFunc);
      }
   }
}

In conclusion, private forwards only adds a more explicit management of forward registration that is not necessary as forwards are called efficiently
« Last Edit: March 22, 2016, 09:29:05 PM by Ownz »

Necavi

  • Sr. Member
  • ****
  • Posts: 499
    • View Profile
I think that Diablo is under the impression that a few hundred races doing a single if (race == thisRaceID) will slow the server down more than one race calculating things poorly.

Ownz

  • Developer
  • Administrator
  • Hero Member
  • *****
  • Posts: 2437
  • chmod -R 777 *
    • View Profile
    • OwnageClan
calls and integer comparisons are very fast things, real profiling is needed to find bottlenecks. there is merit in improving performance, but not without evaluating the actual performance gains.

El Diablo

  • Hero Member
  • *****
  • Posts: 1126
    • View Profile
    • War3Evo
El Diablo isn't stupid :P

Are you confused about how it works?



If you have 40 races...

Global forwards == and if 20 races of that 40 call that forward, only 20 races will call that forward.

If you have 40 races...

Private forwards == and if only 1 race of the 20 races registered that forward during game play, then only 1 race will call that forward even though 20 of them have that forward in their source code.

So.. what I'm getting with private forwards is that 20 races of 40 have that code, but you only hooked 1 race, then only 1 race will be called to handle it and not all 20 that implement the code.  In global forwards every single race that implements the forward will be called.



I bet that is easier to understand :)




I'm sure you understand, that you kind of still want that plugin to run in order to "initialize a player", or perform some code to clear player values on death or spawn.

I'm not sure how well SourceMod will handle pausing a plugin as it tends not to like to "reload" plugins during a full server game play, so with that experience I would feel it would be more "crash" prone.

 
« Last Edit: March 23, 2016, 03:36:30 AM by El Diablo »

Necavi

  • Sr. Member
  • ****
  • Posts: 499
    • View Profile
Actually it has zero problem reloading plugins...as long as you develop them with that in mind (unloading things on unload, registering them in the right places, etc)

Ownz

  • Developer
  • Administrator
  • Hero Member
  • *****
  • Posts: 2437
  • chmod -R 777 *
    • View Profile
    • OwnageClan
pausing, not reloading

you have 40 races, pause 39 races when not in use, if a player selects a race and its paused, unpause it, plugin should know when it is unpaused and re-do global initialization, or rely on on spawn events

El Diablo

  • Hero Member
  • *****
  • Posts: 1126
    • View Profile
    • War3Evo
pausing, not reloading

you have 40 races, pause 39 races when not in use, if a player selects a race and its paused, unpause it, plugin should know when it is unpaused and re-do global initialization, or rely on on spawn events

I looked thru the code and api for handling pausing a plugin and I am not seeming to find it.  Can you give me the specific api to pause a plugin and unpause a plugin?

The only thing I could find was SetFailState: "Causes the plugin to enter a failed state. An error will be thrown and the plugin will be paused until it is unloaded or reloaded."

That could cause some issues in a production server.  Like I've said before, Since recent updates to Sourcemod... I've noticed it is less stable when trying to reload a plugin during a full server of "real" players  and not BOTS. 

The reload stuff looks good on paper, but in a real server full of real players it has a very high rate to crash or lag the server to appear to crash yet causes players to leave cause they think your server is crashing because their client starts a disconnect count down.
« Last Edit: March 24, 2016, 03:51:27 AM by El Diablo »

Ownz

  • Developer
  • Administrator
  • Hero Member
  • *****
  • Posts: 2437
  • chmod -R 777 *
    • View Profile
    • OwnageClan
i don't see a API to set plugin state either, may have to rely on ServerCommand