forked from 0ad/0ad
Ykkrosh
d39a4fac21
# Attempted to make compilation faster by including as little as possible in some .h files, and moving it into .cpp. Fixed BaseTechCollection memory leak. This was SVN commit r3992.
516 lines
14 KiB
C++
516 lines
14 KiB
C++
#include "precompiled.h"
|
|
#include "BaseTech.h"
|
|
#include "BaseTechCollection.h"
|
|
#include "EntityManager.h"
|
|
#include "ps/CStr.h"
|
|
#include "ps/CLogger.h"
|
|
#include "scripting/ScriptingHost.h"
|
|
#include "ps/XML/Xeromyces.h"
|
|
#include "ps/XML/XeroXMB.h"
|
|
#include "BaseEntity.h"
|
|
#include "Entity.h"
|
|
#include "ps/Player.h"
|
|
|
|
#define LOG_CATEGORY "Techs"
|
|
|
|
STL_HASH_SET<CStr, CStr_hash_compare> CBaseTech::m_scriptsLoaded;
|
|
|
|
CBaseTech::CBaseTech()
|
|
{
|
|
ONCE( ScriptingInit(); );
|
|
|
|
m_researched = m_excluded=false;
|
|
m_effectFunction = NULL;
|
|
m_JSFirst = false;
|
|
}
|
|
bool CBaseTech::loadXML( CStr filename )
|
|
{
|
|
CXeromyces XeroFile;
|
|
|
|
if (XeroFile.Load(filename) != PSRETURN_OK)
|
|
return false;
|
|
|
|
#define EL(x) int el_##x = XeroFile.getElementID(#x)
|
|
|
|
EL(tech);
|
|
EL(id);
|
|
EL(req);
|
|
EL(effect);
|
|
|
|
#undef EL
|
|
|
|
XMBElement Root = XeroFile.getRoot();
|
|
if ( Root.getNodeName() != el_tech )
|
|
{
|
|
LOG( ERROR, LOG_CATEGORY, "CBaseTech: XML root was not \"Tech\" in file %s. Load failed.", filename.c_str() );
|
|
return false;
|
|
}
|
|
XMBElementList RootChildren = Root.getChildNodes();
|
|
bool ret;
|
|
for ( int i=0; i<RootChildren.Count; ++i )
|
|
{
|
|
XMBElement element = RootChildren.item(i);
|
|
int name = element.getNodeName();
|
|
if ( name == el_id )
|
|
ret = loadELID( element, XeroFile );
|
|
else if ( name == el_req )
|
|
ret = loadELReq( element, XeroFile );
|
|
else if ( name == el_effect )
|
|
ret = loadELEffect( element, XeroFile, filename );
|
|
else
|
|
continue;
|
|
if ( !ret )
|
|
{
|
|
LOG( ERROR, LOG_CATEGORY, "CBaseTech: Load failed for file %s", filename.c_str() );
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
bool CBaseTech::loadELID( XMBElement ID, CXeromyces& XeroFile )
|
|
{
|
|
#define EL(x) int el_##x = XeroFile.getElementID(#x)
|
|
|
|
EL(generic);
|
|
EL(specific);
|
|
EL(icon);
|
|
EL(icon_cell);
|
|
EL(classes);
|
|
EL(rollover);
|
|
EL(history);
|
|
|
|
#undef EL
|
|
|
|
XMBElementList children = ID.getChildNodes();
|
|
for ( int i=0; i<children.Count; ++i )
|
|
{
|
|
XMBElement element = children.item(i);
|
|
int name = element.getNodeName();
|
|
CStr value = CStr(element.getText());
|
|
|
|
if ( name == el_generic )
|
|
m_Generic = value;
|
|
else if ( name == el_specific )
|
|
m_Specific = value;
|
|
else if ( name == el_icon )
|
|
m_Icon = value;
|
|
else if ( name == el_icon_cell )
|
|
m_IconCell = value.ToInt();
|
|
else if ( name == el_classes )
|
|
m_Classes = value;
|
|
else if ( name == el_rollover )
|
|
continue;
|
|
else if ( name == el_history )
|
|
m_History = value;
|
|
else
|
|
{
|
|
const char* tagName = XeroFile.getElementString(name).c_str();
|
|
LOG( ERROR, LOG_CATEGORY, "CBaseTech: invalid tag %s for XML file", tagName );
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
bool CBaseTech::loadELReq( XMBElement Req, CXeromyces& XeroFile )
|
|
{
|
|
#define EL(x) int el_##x = XeroFile.getElementID(#x)
|
|
|
|
EL(time);
|
|
EL(resource);
|
|
EL(food);
|
|
EL(tech);
|
|
EL(stone);
|
|
EL(ore);
|
|
EL(wood);
|
|
EL(entity);
|
|
|
|
#undef EL
|
|
|
|
XMBElementList children = Req.getChildNodes();
|
|
for ( int i=0; i<children.Count; ++i )
|
|
{
|
|
XMBElement element = children.item(i);
|
|
int name = element.getNodeName();
|
|
CStr value = element.getText();
|
|
|
|
if ( name == el_time )
|
|
m_ReqTime = value.ToFloat();
|
|
else if ( name == el_resource )
|
|
{
|
|
XMBElementList resChildren = element.getChildNodes();
|
|
for ( int j=0; j<resChildren.Count; ++j )
|
|
{
|
|
XMBElement resElement = resChildren.item(j);
|
|
int resName = resElement.getNodeName();
|
|
CStr resValue = CStr(resElement.getText());
|
|
|
|
if ( resName == el_food ) //NOT LOADED-GET CHILD NODES
|
|
m_ReqFood = resValue.ToFloat();
|
|
else if ( resName == el_wood )
|
|
m_ReqWood = resValue.ToFloat();
|
|
else if ( resName == el_stone )
|
|
m_ReqStone = resValue.ToFloat();
|
|
else if ( resName == el_ore )
|
|
m_ReqOre = resValue.ToFloat();
|
|
else
|
|
{
|
|
const char* tagName = XeroFile.getElementString(name).c_str();
|
|
LOG( ERROR, LOG_CATEGORY, "CBaseTech: invalid tag %s for XML file", tagName );
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
else if ( name == el_entity )
|
|
m_ReqEntities.push_back( value );
|
|
else if ( name == el_tech )
|
|
m_ReqTechs.push_back( value );
|
|
else
|
|
{
|
|
const char* tagName = XeroFile.getElementString(name).c_str();
|
|
LOG( ERROR, LOG_CATEGORY, "CBaseTech: invalid tag %s for XML file", tagName );
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
bool CBaseTech::loadELEffect( XMBElement effect, CXeromyces& XeroFile, CStr& filename )
|
|
{
|
|
#define EL(x) int el_##x = XeroFile.getElementID(#x)
|
|
#define AT(x) int at_##x = XeroFile.getAttributeID(#x)
|
|
|
|
EL(target);
|
|
EL(pair);
|
|
EL(modifier);
|
|
EL(attribute);
|
|
EL(value);
|
|
EL(set);
|
|
EL(script);
|
|
EL(function);
|
|
AT(name);
|
|
AT(order);
|
|
AT(file);
|
|
|
|
#undef EL
|
|
#undef AT
|
|
|
|
XMBElementList children = effect.getChildNodes();
|
|
for ( int i=0; i<children.Count; ++i )
|
|
{
|
|
XMBElement element = children.item(i);
|
|
int name = element.getNodeName();
|
|
CStr value = element.getText();
|
|
|
|
if ( name == el_target )
|
|
m_Targets.push_back(value);
|
|
else if ( name == el_pair )
|
|
m_Pairs.push_back(value);
|
|
|
|
else if ( name == el_modifier )
|
|
{
|
|
XMBElementList modChildren = element.getChildNodes();
|
|
m_Modifiers.push_back(Modifier());
|
|
for ( int j=0; j<modChildren.Count; ++j )
|
|
{
|
|
XMBElement modElement = modChildren.item(j);
|
|
CStr modValue = CStr(modElement.getText());
|
|
|
|
if ( modElement.getNodeName() == el_attribute)
|
|
{
|
|
if ( CEntity::m_AttributeTable.find( modValue ) == CEntity::m_AttributeTable.end() )
|
|
{
|
|
LOG( ERROR, LOG_CATEGORY, "CBaseTech::loadXML invalid attribute %s for modifier attribute", modValue);
|
|
m_Modifiers.pop_back();
|
|
return false;
|
|
}
|
|
else
|
|
m_Modifiers.back().attribute = modValue;
|
|
}
|
|
else if ( modElement.getNodeName() == el_value )
|
|
m_Modifiers.back().value = modValue.ToFloat();
|
|
else
|
|
{
|
|
LOG( ERROR, LOG_CATEGORY, "CBaseTech::loadXML invalid tag inside \"Modifier\" tag" );
|
|
m_Modifiers.pop_back();
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
|
|
else if ( name == el_set )
|
|
{
|
|
XMBElementList setChildren = element.getChildNodes();
|
|
m_Sets.push_back(Modifier());
|
|
for ( int j=0; j<setChildren.Count; ++j )
|
|
{
|
|
XMBElement setElement = setChildren.item(j);
|
|
CStr setValue = CStr(setElement.getText());
|
|
|
|
if ( setElement.getNodeName() == el_attribute)
|
|
{
|
|
if ( CEntity::m_AttributeTable.find( setValue ) == CEntity::m_AttributeTable.end() )
|
|
{
|
|
LOG( ERROR, LOG_CATEGORY, "CBaseTech::loadXML invalid attribute %s for \"set\" attribute", setValue);
|
|
m_Sets.pop_back();
|
|
return false;
|
|
}
|
|
else
|
|
m_Sets.back().attribute = setValue;
|
|
}
|
|
else if ( setElement.getNodeName() == el_value )
|
|
m_Sets.back().value = setValue.ToFloat();
|
|
else
|
|
{
|
|
LOG( ERROR, LOG_CATEGORY, "CBaseTech::loadXML invalid tag inside \"Set\" tag" );
|
|
m_Sets.pop_back();
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
|
|
else if ( name == el_script )
|
|
{
|
|
CStr Include = element.getAttributes().getNamedItem( at_file );
|
|
if( Include.Length() && m_scriptsLoaded.find( Include ) == m_scriptsLoaded.end() )
|
|
{
|
|
m_scriptsLoaded.insert( Include );
|
|
g_ScriptingHost.RunScript( Include );
|
|
}
|
|
CStr Inline = element.getText();
|
|
if( Inline.Length() )
|
|
{
|
|
g_ScriptingHost.RunMemScript( Inline.c_str(), Inline.Length(), filename, element.getLineNumber() );
|
|
}
|
|
}
|
|
else if ( name == el_function )
|
|
{
|
|
utf16string funcName = element.getAttributes().getNamedItem( at_name );
|
|
CStr Inline = element.getText();
|
|
//default to first
|
|
m_JSFirst = (utf16string() == element.getAttributes().getNamedItem( at_order ));
|
|
|
|
if ( funcName != utf16string() )
|
|
{
|
|
jsval fnval;
|
|
JSBool ret = JS_GetUCProperty( g_ScriptingHost.GetContext(), g_ScriptingHost.GetGlobalObject(), funcName.c_str(), funcName.size(), &fnval );
|
|
debug_assert( ret );
|
|
JSFunction* fn = JS_ValueToFunction( g_ScriptingHost.GetContext(), fnval );
|
|
if( !fn )
|
|
{
|
|
LOG( ERROR, LOG_CATEGORY, "CBaseTech::LoadXML: Function does not exist for %hs in file %s. Load failed.", funcName.c_str(), filename.c_str() );
|
|
return false;
|
|
}
|
|
m_effectFunction->SetFunction( fn );
|
|
}
|
|
else if ( Inline != CStr() )
|
|
m_effectFunction->Compile( CStrW( filename ) + L"::" + (CStrW)funcName + L" (" + CStrW( element.getLineNumber() ) + L")", Inline );
|
|
//(No error needed; scripts are optional)
|
|
}
|
|
else
|
|
{
|
|
const char* tagName = XeroFile.getElementString(name).c_str();
|
|
LOG( ERROR, LOG_CATEGORY, "CBaseTech: invalid tag %s for XML file", tagName );
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
bool CBaseTech::isTechValid()
|
|
{
|
|
if ( m_excluded )
|
|
return false;
|
|
if ( hasReqEntities() && hasReqTechs() )
|
|
return true;
|
|
return false;
|
|
}
|
|
bool CBaseTech::hasReqEntities()
|
|
{
|
|
bool ret=true;
|
|
std::vector<HEntity>* entities = m_player->GetControlledEntities();
|
|
for ( std::vector<CStr>::iterator it=m_ReqEntities.begin(); it != m_ReqEntities.end(); it++ )
|
|
{
|
|
for( CEntityList::iterator it2=entities->begin(); it2 != entities->end(); it2++ )
|
|
{
|
|
if ( (*it2)->m_classes.IsMember(*it) )
|
|
{
|
|
ret=true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
delete entities;
|
|
return ret;
|
|
}
|
|
bool CBaseTech::hasReqTechs()
|
|
{
|
|
bool ret=true;
|
|
for ( std::vector<CStr>::iterator it=m_ReqTechs.begin(); it != m_ReqTechs.end(); it++ )
|
|
{
|
|
if ( !g_BaseTechCollection.getTemplate( (CStrW)*it )->isResearched() )
|
|
{
|
|
ret=false;
|
|
break;
|
|
}
|
|
}
|
|
return ret;
|
|
}
|
|
//JS stuff
|
|
|
|
void CBaseTech::ScriptingInit()
|
|
{
|
|
AddProperty(L"generic", &CBaseTech::m_Generic, true);
|
|
AddProperty(L"specific", &CBaseTech::m_Specific, true);
|
|
AddProperty(L"icon", &CBaseTech::m_Icon); //GUI might want to change this...?
|
|
AddProperty<int>(L"icon_cell", &CBaseTech::m_IconCell);
|
|
AddProperty(L"classes", &CBaseTech::m_Classes, true);
|
|
AddProperty(L"history", &CBaseTech::m_History, true);
|
|
|
|
AddProperty<float>(L"time", &CBaseTech::m_ReqTime); //Techs may upgrade research time and cost of other techs
|
|
AddProperty<float>(L"food", &CBaseTech::m_ReqFood);
|
|
AddProperty<float>(L"wood", &CBaseTech::m_ReqWood);
|
|
AddProperty<float>(L"stone", &CBaseTech::m_ReqStone);
|
|
AddProperty<float>(L"ore", &CBaseTech::m_ReqOre);
|
|
|
|
AddMethod<jsval, &CBaseTech::ApplyEffects>( "applyEffects", 1 );
|
|
AddMethod<jsval, &CBaseTech::IsExcluded>( "isExcluded", 0 );
|
|
AddMethod<jsval, &CBaseTech::IsValid>( "isValid", 0 );
|
|
AddMethod<jsval, &CBaseTech::IsResearched>( "isResearched", 0 );
|
|
AddMethod<jsval, &CBaseTech::GetPlayerID>( "getPlayerID", 0 );
|
|
AddMethod<jsval, &CBaseTech::IsJSFirst>( "isJSFirst", 0 );
|
|
|
|
CJSObject<CBaseTech>::ScriptingInit("TechTemplate");
|
|
|
|
debug_printf("CBaseTech::ScriptingInit complete");
|
|
}
|
|
|
|
jsval CBaseTech::ApplyEffects( JSContext* cx, uintN argc, jsval* argv )
|
|
{
|
|
if ( !isTechValid() )
|
|
return JS_FALSE;
|
|
else if ( argc < 2 )
|
|
{
|
|
JS_ReportError(cx, "too few parameters for CBaseTech::ApplyEffects.");
|
|
return JS_FALSE;
|
|
}
|
|
|
|
//Order overriding for some special case
|
|
bool first = ToPrimitive<bool>( argv[0] );
|
|
bool invert = ToPrimitive<bool>( argv[1] );
|
|
|
|
//Optional type overriding if some in some special case the script wants to modify non-floats
|
|
CStr varType("float");
|
|
if ( argc == 3 )
|
|
varType = ToPrimitive<CStr>( argv[2] );
|
|
|
|
if ( first )
|
|
if( m_effectFunction )
|
|
m_effectFunction->Run( this->GetScript() );
|
|
|
|
//Disable other templates
|
|
for ( std::vector<CStr>::iterator it=m_Pairs.begin(); it != m_Pairs.end(); it++ )
|
|
g_BaseTechCollection.getTemplate(*it)->setExclusion(true);
|
|
|
|
std::vector<HEntity>* entities = m_player->GetControlledEntities();
|
|
if ( entities->empty() )
|
|
{
|
|
delete entities;
|
|
return JS_FALSE;
|
|
}
|
|
|
|
std::vector<HEntity> entitiesAffected;
|
|
|
|
//Find which entities should be affected
|
|
for ( size_t i=0; i<entities->size(); ++i )
|
|
{
|
|
for ( std::vector<CStr>::iterator it = m_Targets.begin(); it != m_Targets.end(); it++ )
|
|
{
|
|
if ( (*entities)[i]->m_classes.IsMember( *it ) )
|
|
{
|
|
entitiesAffected.push_back( (*entities)[i] );
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
std::vector<HEntity>::iterator HEit = entitiesAffected.begin();
|
|
for ( ; HEit != entitiesAffected.end(); HEit++ )
|
|
{
|
|
for ( std::vector<Modifier>::iterator mod=m_Modifiers.begin(); mod!=m_Modifiers.end(); mod++ )
|
|
{
|
|
//CEntity* ent = *HEit;
|
|
//debug_printf("Modifying on %ls\n", ent->m_base->m_Tag.c_str() );
|
|
|
|
//Get the member corresponding to the javascript attribute string
|
|
void* attribute = (char*)&**HEit + (*HEit)->m_AttributeTable[mod->attribute];
|
|
float modValue = (invert ? -mod->value : mod->value);
|
|
|
|
if ( varType == "int" )
|
|
*(int*)attribute += (int)modValue;
|
|
if ( varType == "double" )
|
|
*(double*)attribute += (double)modValue;
|
|
else
|
|
*(float*)attribute += (float)modValue;
|
|
}
|
|
}
|
|
|
|
for ( HEit = entitiesAffected.begin(); HEit != entitiesAffected.end(); HEit++ )
|
|
{
|
|
for ( std::vector<Modifier>::iterator set=m_Sets.begin(); set!=m_Sets.end(); set++ )
|
|
{
|
|
//CEntity* ent = *HEit;
|
|
//debug_printf("Setting on %ls\n", ent->m_base->m_Tag.c_str() );
|
|
|
|
//Get the member corresponding to the javascript attribute string
|
|
void* attribute = (char*)&**HEit + (*HEit)->m_AttributeTable[set->attribute];
|
|
float setValue = invert ? -set->value : set->value;
|
|
|
|
if ( varType == "int" )
|
|
*(int*)attribute = (int)setValue;
|
|
if ( varType == "double" )
|
|
*(double*)attribute = (double)setValue;
|
|
else
|
|
*(float*)attribute = (float)setValue;
|
|
}
|
|
}
|
|
|
|
if ( !first )
|
|
if( m_effectFunction )
|
|
m_effectFunction->Run( this->GetScript() );
|
|
delete entities;
|
|
debug_printf("Done! I think\n");
|
|
return JS_TRUE;
|
|
}
|
|
|
|
jsval CBaseTech::IsValid( JSContext* UNUSED(cx), uintN UNUSED(argc), jsval* UNUSED(argv) )
|
|
{
|
|
if ( isTechValid() )
|
|
return JS_TRUE;
|
|
return JS_FALSE;
|
|
}
|
|
jsval CBaseTech::IsExcluded( JSContext* UNUSED(cx), uintN UNUSED(argc), jsval* UNUSED(argv) )
|
|
{
|
|
if ( m_excluded )
|
|
return JS_TRUE;
|
|
return JS_FALSE;
|
|
}
|
|
jsval CBaseTech::IsResearched( JSContext* UNUSED(cx), uintN UNUSED(argc), jsval* UNUSED(argv) )
|
|
{
|
|
if ( isResearched() )
|
|
return JS_TRUE;
|
|
return JS_FALSE;
|
|
}
|
|
inline jsval CBaseTech::GetPlayerID( JSContext* UNUSED(cx), uintN UNUSED(argc), jsval* UNUSED(argv) )
|
|
{
|
|
return ToJSVal( m_player->GetPlayerID() );
|
|
}
|
|
jsval CBaseTech::IsJSFirst( JSContext* UNUSED(cx), uintN UNUSED(argc), jsval* UNUSED(argv) )
|
|
{
|
|
if ( m_JSFirst )
|
|
return JS_TRUE;
|
|
return JS_FALSE;
|
|
}
|
|
|
|
|
|
|
|
|