I decided to do this now because i know that it will be a while before i can get to this if i make it part of SharpLife, and since i have to reconstruct the original C++ code anyway before i can convert it i figured i'd get it done now, so people can use it and find any mistakes/bugs before it gets converted.
So far i've implemented CTF gamerules, which is about 2000 lines of code. Other gamerules need to be updated, and the co-op gamerules need to be implemented as well.
I'm going to start with the NPCs now, since that's the interesting part and because that's what the largest part is.
Implementation is based on what's needed to make single maps work, starting with the training maps, then the campaign, then multiplayer. This way it can be tested more easily.
The ultimate goal is to make an SDK that completely implements Opposing Force. You won't be able to copy paste it into your own mod unfortunately since it changes existing SDK code (some network messages are different, gamerules are different too). When the time comes i'll port it to SharpLife, integrating all of the features so that you can play Opposing Force as well as Half-Life using the same base game, and use any features present in either in your maps.
I'm not doing a 1:1 reconstruction, there are places where i'm making use of newer language features and simpler code to make things easier. For example, iterating over players is done like this:
int teamCount[ MaxTeams ] = {};
for( auto pPlayer : UTIL_FindPlayers() )
{
if( pPlayer->m_iTeamNum != CTFTeam::None )
{
++teamCount[ ( ( int ) pPlayer->m_iTeamNum ) - 1 ];
}
}
I'm also using enum class, as you can see here. It keeps things cleaner and adds type checking, though it does have some drawbacks.Iterating over entities is similar:
auto useDefault = true;
for( auto pEquip : UTIL_FindEntitiesByClassname( "game_player_equip" ) )
{
useDefault = false;
pEquip->Touch( pPlayer );
}
It's basically the same as before, but it's easier to use and you can't screw up the logic, like off by one errors in player iteration or a forgotten null check. It's also type safe if you need a specific class:
for( auto pFlag : UTIL_FindEntitiesByClassname<CTFGoalFlag>( "item_ctfflag" ) )
{
if( pFlag->m_iGoalState == 2 )
{
pFlag->TurnOnLight( pPlayer );
}
}
(Not all of the constants have been defined yet)I've also added helpers to make it easier to do type conversions:
auto pPlayer = GET_PRIVATE<CBasePlayer>( pItem->pev->owner );
auto pOtherPlayer = CBaseEntity::Instance<CBasePlayer>( tr.pHit );
auto pFlag = pPlayer->m_pFlag.Entity<CTFGoalFlag>();
This eliminates the casts used otherwise, and also allows you to swap out the static_cast
with a dynamic_cast
to help find incorrect casts.