Gathering and unit training (engine-side); also other minor improvements

This was SVN commit r2132.
This commit is contained in:
MarkT 2005-04-15 04:23:33 +00:00
parent 79e1c273e6
commit 2120576bc7
30 changed files with 670 additions and 300 deletions

View File

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="iso-8859-1" standalone="no"?>
<Entity Parent="template_gaia_flora">
<Entity Parent="template_gaia_flora" Corpse="template_dead_tree">
<Traits>
<Id
generic="Tree"

View File

@ -29,6 +29,7 @@
list=""
list.unit="isw_b;isp_b;ijv_b;iar_b;isl_b;csw_b;csp_b;cjv_b;car_b;fem"
list.tech="fem"
construct="1"
/>
</Actions>
<Footprint Width="24.0" Height="24.0"/>

View File

@ -16,158 +16,17 @@
<Up
rank="0"
/>
<Creation
time="20"
/>
</Traits>
<Event On="Attack">
<![CDATA[
curr_hit = getGUIGlobal().newRandomSound("voice", "hit", this.traits.audio.path);
curr_hit.play();
<Script File="entities/template_entity_script.js" />
// Attack logic.
dmg = new DamageType();
dmg.crush = parseInt(this.actions.attack.damage * this.actions.attack.crush);
dmg.hack = parseInt(this.actions.attack.damage * this.actions.attack.hack);
dmg.pierce = parseInt(this.actions.attack.damage * this.actions.attack.pierce);
<Event On="Attack" Function="entity_event_attack" />
<Event On="Gather" Function="entity_event_gather" />
<Event On="TakesDamage" Function="entity_event_takesdamage" />
<Event On="TargetChanged" Function="entity_event_targetchanged" />
<Event On="PrepareOrder" Function="entity_event_prepareorder" />
evt.target.damage( dmg, this );
]]>
</Event>
<Event On="TakesDamage">
<![CDATA[
// Apply armour and work out how much damage we actually take
crushDamage = parseInt(evt.damage.crush - this.traits.armour.value * this.traits.armour.crush);
if ( crushDamage < 0 ) crushDamage = 0;
pierceDamage = parseInt(evt.damage.pierce - this.traits.armour.value * this.traits.armour.pierce);
if ( pierceDamage < 0 ) pierceDamage = 0;
hackDamage = parseInt(evt.damage.hack - this.traits.armour.value * this.traits.armour.hack);
if ( hackDamage < 0 ) hackDamage = 0;
totalDamage = parseInt(evt.damage.typeless + crushDamage + pierceDamage + hackDamage);
// Minimum of 1 damage
if( totalDamage < 1 ) totalDamage = 1;
this.traits.health.curr -= totalDamage;
if( this.traits.health.curr <= 0 )
{
// If the inflictor gains promotions, and he's capable of earning more ranks,
if (evt.inflictor.traits.up && evt.inflictor.traits.up.curr && evt.inflictor.traits.up.req && evt.inflictor.traits.up.newentity && evt.inflictor.traits.up.newentity != "")
{
// Give him the fallen's upgrade points (if he has any).
if (this.traits.loot.up)
evt.inflictor.traits.up.curr = parseInt(evt.inflictor.traits.up.curr) + parseInt(this.traits.loot.up);
// Notify player.
if (this.traits.id.specific)
console.write(this.traits.id.specific + " has earned " + this.traits.loot.up + " upgrade points!");
else
console.write("One of your units has earned " + this.traits.loot.up + " upgrade points!");
// If he now has maximum upgrade points for his rank,
if (evt.inflictor.traits.up.curr >= evt.inflictor.traits.up.req)
{
// Notify the player.
if (this.traits.id.specific)
console.write(this.traits.id.specific + " has gained a promotion!");
else
console.write("One of your units has gained a promotion!");
// Reset his upgrade points.
evt.inflictor.traits.up.curr = 0;
// Transmogrify him into his next rank.
evt.inflictor.template = getEntityTemplate(evt.inflictor.traits.up.newentity);
}
}
// If the fallen is worth any loot,
if (this.traits.loot && (this.traits.loot.food || this.traits.loot.wood || this.traits.loot.stone || this.traits.loot.ore))
{
// Give the inflictor his resources.
if (this.traits.loot.food)
getGUIGlobal().GiveResources("Food", parseInt(this.traits.loot.food));
if (this.traits.loot.wood)
getGUIGlobal().GiveResources("Wood", parseInt(this.traits.loot.wood));
if (this.traits.loot.stone)
getGUIGlobal().GiveResources("Stone", parseInt(this.traits.loot.stone));
if (this.traits.loot.ore)
getGUIGlobal().GiveResources("Ore", parseInt(this.traits.loot.ore));
}
// Notify player.
if( evt.inflictor )
console.write( this.traits.id.generic + " got the point of " + evt.inflictor.traits.id.generic + "'s Gladius." );
else
console.write( this.traits.id.generic + " died in mysterious circumstances." );
// Make him cry out in pain.
curr_pain = getGUIGlobal().newRandomSound("voice", "pain", this.traits.audio.path);
curr_pain.play();
// We've taken what we need. Kill the swine.
this.kill();
}
else if( evt.inflictor && this.actions.attack )
{
// If we're not already doing something else, take a measured response - hit 'em back.
// You know, I think this is quite possibly the first AI code the AI divlead has written
// for 0 A.D....
if( this.isIdle() )
this.order( ORDER_ATTACK, evt.inflictor );
}
]]>
</Event>
<Event On="TargetChanged">
<![CDATA[
// This event lets us know when the user moves his/her cursor to a different unit (provided this
// unit is selected) - use it to tell the engine what context cursor should be displayed, given
// the target.
// Attack iff there's a target, it's our enemy, and we're armed. Otherwise move.
// ToString is needed because every property is actually an object (though that's usually
// hidden from you) and comparing an object to any other object in JavaScript (1.5, at least)
// yields false. ToString converts them to their actual values (i.e. the four character
// string) first.
if( evt.target &&
this.actions.attack &&
( evt.target.traits.id.civ_code != "gaia" ) &&
( evt.target.traits.id.civ_code.toString() != this.traits.id.civ_code.toString() ) )
evt.defaultAction = ORDER_ATTACK;
else
evt.defaultAction = ORDER_GOTO;
]]>
</Event>
<Event On="PrepareOrder">
<![CDATA[
// This event gives us a chance to veto any order we're given before we execute it.
// Not sure whether this really belongs here like this: the alternative is to override it in
// subtypes - then you wouldn't need to check tags, you could hardcode results.
switch( evt.orderType )
{
case ORDER_GOTO:
if( !this.actions.move )
evt.preventDefault();
break;
case ORDER_PATROL:
if( !this.actions.patrol )
evt.preventDefault();
break;
case ORDER_ATTACK:
// If we can't attack, we're not targeting a unit, or that unit is the same civ as us.
// (Should of course be same /player/ as us - not ready yet.)
if( !this.actions.attack ||
!evt.target ||
( evt.target.traits.id.civ_code.toString() == this.traits.id.civ_code.toString() ) )
evt.preventDefault();
break;
default:
evt.preventDefault();
}
]]>
</Event>
<Actions>
</Actions>
<Actions />
</Entity>

View File

@ -0,0 +1,257 @@
function entity_event_attack( evt )
{
curr_hit = getGUIGlobal().newRandomSound("voice", "hit", this.traits.audio.path);
curr_hit.play();
// Attack logic.
dmg = new DamageType();
dmg.crush = parseInt(this.actions.attack.damage * this.actions.attack.crush);
dmg.hack = parseInt(this.actions.attack.damage * this.actions.attack.hack);
dmg.pierce = parseInt(this.actions.attack.damage * this.actions.attack.pierce);
evt.target.damage( dmg, this );
}
function entity_event_gather( evt )
{
gather_amt = parseInt( this.actions.gather[evt.target.traits.supply.type].speed );
if( evt.target.traits.supply.max > 0 )
{
if( evt.target.traits.supply.curr <= gather_amt )
{
gather_amt = evt.target.traits.supply.curr;
evt.target.kill();
}
console.write( evt.target.traits.supply.type );
console.write( evt.target.traits.supply.type.toString().toUpperCase() );
evt.target.traits.supply.curr -= gather_amt;
this.player.resource.valueOf()[evt.target.traits.supply.type.toString().toUpperCase()] += gather_amt;
}
}
function entity_event_takesdamage( evt )
{
// Apply armour and work out how much damage we actually take
crushDamage = parseInt(evt.damage.crush - this.traits.armour.value * this.traits.armour.crush);
if ( crushDamage < 0 ) crushDamage = 0;
pierceDamage = parseInt(evt.damage.pierce - this.traits.armour.value * this.traits.armour.pierce);
if ( pierceDamage < 0 ) pierceDamage = 0;
hackDamage = parseInt(evt.damage.hack - this.traits.armour.value * this.traits.armour.hack);
if ( hackDamage < 0 ) hackDamage = 0;
totalDamage = parseInt(evt.damage.typeless + crushDamage + pierceDamage + hackDamage);
// Minimum of 1 damage
if( totalDamage < 1 ) totalDamage = 1;
this.traits.health.curr -= totalDamage;
if( this.traits.health.curr <= 0 )
{
// If the inflictor gains promotions, and he's capable of earning more ranks,
if (evt.inflictor.traits.up && evt.inflictor.traits.up.curr && evt.inflictor.traits.up.req && evt.inflictor.traits.up.newentity && evt.inflictor.traits.up.newentity != "")
{
// Give him the fallen's upgrade points (if he has any).
if (this.traits.loot.up)
evt.inflictor.traits.up.curr = parseInt(evt.inflictor.traits.up.curr) + parseInt(this.traits.loot.up);
// Notify player.
if (this.traits.id.specific)
console.write(this.traits.id.specific + " has earned " + this.traits.loot.up + " upgrade points!");
else
console.write("One of your units has earned " + this.traits.loot.up + " upgrade points!");
// If he now has maximum upgrade points for his rank,
if (evt.inflictor.traits.up.curr >= evt.inflictor.traits.up.req)
{
// Notify the player.
if (this.traits.id.specific)
console.write(this.traits.id.specific + " has gained a promotion!");
else
console.write("One of your units has gained a promotion!");
// Reset his upgrade points.
evt.inflictor.traits.up.curr = 0;
// Transmogrify him into his next rank.
evt.inflictor.template = getEntityTemplate(evt.inflictor.traits.up.newentity);
}
}
// If the fallen is worth any loot,
if (this.traits.loot && (this.traits.loot.food || this.traits.loot.wood || this.traits.loot.stone || this.traits.loot.ore))
{
// Give the inflictor his resources.
if (this.traits.loot.food)
getGUIGlobal().GiveResources("Food", parseInt(this.traits.loot.food));
if (this.traits.loot.wood)
getGUIGlobal().GiveResources("Wood", parseInt(this.traits.loot.wood));
if (this.traits.loot.stone)
getGUIGlobal().GiveResources("Stone", parseInt(this.traits.loot.stone));
if (this.traits.loot.ore)
getGUIGlobal().GiveResources("Ore", parseInt(this.traits.loot.ore));
}
// Notify player.
if( evt.inflictor )
console.write( this.traits.id.generic + " got the point of " + evt.inflictor.traits.id.generic + "'s Gladius." );
else
console.write( this.traits.id.generic + " died in mysterious circumstances." );
// Make him cry out in pain.
curr_pain = getGUIGlobal().newRandomSound("voice", "pain", this.traits.audio.path);
curr_pain.play();
// We've taken what we need. Kill the swine.
this.kill();
}
else if( evt.inflictor && this.actions.attack )
{
// If we're not already doing something else, take a measured response - hit 'em back.
// You know, I think this is quite possibly the first AI code the AI divlead has written
// for 0 A.D....
if( this.isIdle() )
this.order( ORDER_ATTACK, evt.inflictor );
}
}
function entity_event_targetchanged( evt )
{
// This event lets us know when the user moves his/her cursor to a different unit (provided this
// unit is selected) - use it to tell the engine what context cursor should be displayed, given
// the target.
// Attack iff there's a target, it's our enemy, and we're armed. Otherwise, if we can gather, and
// the target supplies, gather. If all else fails, move.
// ToString is needed because every property is actually an object (though that's usually
// hidden from you) and comparing an object to any other object in JavaScript (1.5, at least)
// yields false. ToString converts them to their actual values (i.e. the four character
// string) first.
evt.defaultAction = ORDER_GOTO;
if( evt.target )
{
if( this.actions.attack &&
( evt.target.traits.id.civ_code != "gaia" ) &&
( evt.target.traits.id.civ_code.toString() != this.traits.id.civ_code.toString() ) )
evt.defaultAction = ORDER_ATTACK;
if( this.actions.gather && evt.target.traits.supply &&
this.actions.gather.valueOf()[evt.target.traits.supply.type] &&
( ( evt.target.traits.supply.curr > 0 ) || ( evt.target.traits.supply.max == 0 ) ) )
evt.defaultAction = ORDER_GATHER;
}
}
function entity_event_prepareorder( evt )
{
// This event gives us a chance to veto any order we're given before we execute it.
// Not sure whether this really belongs here like this: the alternative is to override it in
// subtypes - then you wouldn't need to check tags, you could hardcode results.
switch( evt.orderType )
{
case ORDER_GOTO:
if( !this.actions.move )
evt.preventDefault();
break;
case ORDER_PATROL:
if( !this.actions.patrol )
evt.preventDefault();
break;
case ORDER_ATTACK:
// If we can't attack, we're not targeting a unit, or that unit is the same civ as us.
// (Should of course be same /player/ as us - not ready yet.)
if( !this.actions.attack ||
!evt.target ||
( evt.target.traits.id.civ_code.toString() == this.traits.id.civ_code.toString() ) )
evt.preventDefault();
break;
case ORDER_GATHER:
if( !this.actions.gather ||
!this.actions.gather.valueOf()[evt.target.traits.supply.type] ||
( ( evt.target.traits.supply.curr == 0 ) && ( evt.target.traits.supply.max > 0 ) ) )
evt.preventDefault();
default:
evt.preventDefault();
}
}
function entity_add_create_queue( template )
{
// Make sure we have a queue to put things in...
if( !this.actions.create.queue )
this.actions.create.queue = new Array();
// Append to the end of this queue
this.actions.create.queue.valueOf().push( template );
// If we're not already building something...
if( !this.actions.create.progress || !this.actions.create.progress.valueOf() )
{
console.write( "Starting work on (unqueued) ", template.tag );
// Start the progress timer.
// - First parameter is target value (in this case, base build time in seconds)
// - Second parameter is increment per millisecond (use build rate modifier and correct for milliseconds)
// - Third parameter is the function to call when the timer finishes.
// - Fourth parameter is the scope under which to run that function (what the 'this' parameter should be)
this.actions.create.progress = new ProgressTimer( template.traits.creation.time, this.actions.create.construct / 1000, entity_create_complete, this )
}
}
// This is the syntax to add a function (or a property) to absolutely every entity.
Entity.prototype.add_create_queue = entity_add_create_queue;
function entity_create_complete()
{
// Get the unit that was at the head of our queue, and remove it.
// (Oh, for information about all these nifty properties and functions
// of the Array object that I use, see the ECMA-262 documentation
// at http://www.mozilla.org/js/language/E262-3.pdf. Bit technical but
// the sections on 'Native ECMAScript Objects' are quite useful)
var template = this.actions.create.queue.valueOf().shift();
// Code to find a free space around an object is tedious and slow, so
// I wrote it in C. Takes the template object so it can determine how
// much space it needs to leave.
position = this.getSpawnPoint( template );
// The above function returns null if it couldn't find a large enough space.
if( !position )
{
console.write( "Couldn't train unit - not enough space" );
// Oh well. The player's just lost all the resources and time they put into
// construction - serves them right for not paying attention to the land
// around their barracks, doesn't it?
return;
}
created = new Entity( template, position );
// Above shouldn't ever fail, but just in case...
if( !created )
return;
console.write( "Created: ", template.tag );
// Entities start under Gaia control - make the controller
// the same as our controller
created.player = this.player;
// If there's something else in the build queue...
if( this.actions.create.queue.valueOf().length > 0 )
{
// Start on the next item.
template = this.actions.create.queue.valueOf()[0];
console.write( "Starting work on (queued) ", template.tag );
this.actions.create.progress = new ProgressTimer( template.traits.creation.time, this.actions.create.construct / 1000, entity_create_complete, this )
}
else
{
// Otherwise, delete the timer.
this.actions.create.progress = null;
}
}
function attempt_add_to_build_queue( entity, create_tag )
{
console.write( "Adding ", create_tag, " to build queue..." );
entity.add_create_queue( getEntityTemplate( create_tag ) );
}

View File

@ -58,10 +58,10 @@
food.fruit="1"
food.grain="1"
food.fish="1"
wood="1"
stone="1"
ore="1"
/>
ore="1" >
<Wood speed="1" />
</Gather>
<Repair
rate="1"
/>

View File

@ -12,17 +12,6 @@
</Traits>
<Event On="Initialize">
<![CDATA[
// PASAP wander-between-houses script
houses = entities.subset( "this.template.tag == \"House\"" );
while( houses.length )
{
index = Math.floor( Math.random() * houses.length );
house = houses[index];
console.write( house.position );
this.orderQueued( 4 /* patrol */, house.position.x, house.position.z );
houses.remove( index );
}
console.write( "A new Dude has entered your dungeon." );
]]>
</Event>

View File

@ -49,26 +49,21 @@ function AddResource(resourceName, resourceQty)
{
// Creates a resource type.
// MT: Rewritten to use JavaScript's nice associative-array-alikes. Requires the valueOf() hack - I'm looking into this.
if (!localPlayer.resource)
{
// Define the base resource group if it does not exist.
localPlayer.resource = new Array();
// Define resource total.
localPlayer.resource.last = new Object();
localPlayer.resource.last = 0;
}
// Set resource name to upper-case to ensure it matches resource control name.
resourceName = resourceName.toUpperCase();
// Store resource's name and starting value.
localPlayer.resource[localPlayer.resource.last] = new Object();
localPlayer.resource[localPlayer.resource.last].name = resourceName;
localPlayer.resource[localPlayer.resource.last].qty = resourceQty;
localPlayer.resource.last++;
localPlayer.resource.valueOf()[resourceName] = resourceQty;
console.write("Added " + resourceName + " (" + localPlayer.resource.last + ")");
console.write("Added " + resourceName );
}
// ====================================================================
@ -91,20 +86,17 @@ function GiveResources(resourceName, resourceQty)
{
// Generic function to add resources to the player's Pool.
// Find the resource in the list.
resourceSeek = 0;
while (resourceName != localPlayer.resource[resourceSeek].name && resourceSeek < localPlayer.resource.last)
resourceSeek++;
// MT: Rewritten to use JavaScript's nice associative-array-alikes. Requires the valueOf() hack - I'm looking into this.
if( localPlayer.resource.valueOf()[resourceName] )
{
localPlayer.resource.valueOf()[resourceName] += resourceQty;
console.write("Earned " + resourceQty + " resources.");
return( true );
}
// If the resource wasn't in the list, report an error.
return false;
// Add the quantity to the resource.
localPlayer.resource[resourceSeek].qty += resourceQty;
console.write("Earned " + resourceQty + " resources.");
return true;
}
// ====================================================================
@ -113,20 +105,23 @@ function UpdateResourcePool()
{
// Populate the resource pool with current quantities.
for (resourceSeek = 0; resourceSeek < localPlayer.resource.last; resourceSeek++)
// MT: Rewritten to use JavaScript's nice associative-array-alikes. Requires the valueOf() hack - I'm looking into this.
pool = localPlayer.resource.valueOf();
for( resource in pool )
{
switch (localPlayer.resource[resourceSeek].name.toString())
switch( resource )
{
case "POPULATION":
// If it's population, combine population and housing in one string.
getGUIObjectByName("SN_RESOURCE_COUNTER_POPULATION").caption = localPlayer.resource[resourceSeek].qty + "/" + localPlayer.resource[resourceSeek].qty;
getGUIObjectByName("SN_RESOURCE_COUNTER_POPULATION").caption = pool.POPULATION + "/" + pool.HOUSING;
break;
case "HOUSING":
// Skip housing, as it's handled as a component of population.
break;
default:
// Set the value of a normal resource caption.
getGUIObjectByName("SN_RESOURCE_COUNTER_" + localPlayer.resource[resourceSeek].name).caption = localPlayer.resource[resourceSeek].qty.toString();
getGUIObjectByName("SN_RESOURCE_COUNTER_" + resource ).caption = pool[resource].toString();
break;
}
}

View File

@ -227,7 +227,9 @@ function initStatusOrb()
{
for (SN_STATUS_PANE_COMMAND.list.curr = 1; SN_STATUS_PANE_COMMAND.list.curr <= SN_STATUS_PANE_COMMAND.list.max; SN_STATUS_PANE_COMMAND.list.curr++)
{
SN_STATUS_PANE_COMMAND[SN_STATUS_PANE_COMMAND.list.curr][SN_STATUS_PANE_COMMAND.tab.curr] = addArrayElement(Crd, Crd.last);
// MT: What's going on here?
SN_STATUS_PANE_COMMAND[SN_STATUS_PANE_COMMAND.list.curr][SN_STATUS_PANE_COMMAND.tab.curr] = new Number( addArrayElement(Crd, Crd.last) );
Crd[Crd.last-1].rleft = left_screen; Crd[Crd.last-1].rtop = bottom_screen;
Crd[Crd.last-1].rright = left_screen; Crd[Crd.last-1].rbottom = bottom_screen;
Crd[Crd.last-1].width = crd_portrait_sml_width;
@ -375,10 +377,10 @@ function UpdateList(listIcon, listCol)
setPortrait("SN_STATUS_PANE_COMMAND_" + listCol + "_" + parseInt(createLoop+2), getEntityTemplate(UpdateListEntityName).traits.id.icon, selection[0].traits.id.civ_code, getEntityTemplate(UpdateListEntityName).traits.id.icon_cell);
GUIObjectUnhide("SN_STATUS_PANE_COMMAND_" + listCol + "_" + parseInt(createLoop+2));
// Store content info in tab button for future reference.
SN_STATUS_PANE_COMMAND[parseInt(createLoop+2)][listCol].name = listArray[createLoop];
SN_STATUS_PANE_COMMAND[parseInt(createLoop+2)][listCol].last++;
SN_STATUS_PANE_COMMAND[parseInt(createLoop+2)][listCol].last++;
}
else
GUIObjectHide("SN_STATUS_PANE_COMMAND_" + listCol + "_" + parseInt(createLoop+2));
@ -415,7 +417,7 @@ function UpdateCommand(listIcon, listCol)
SN_STATUS_PANE_COMMAND[1][listCol].type = "command";
SN_STATUS_PANE_COMMAND[1][listCol].last = 0;
SN_STATUS_PANE_COMMAND[1][listCol].name = listIcon;
return (listCol-1);
}
else
@ -444,7 +446,9 @@ function PressCommandButton(list, tab)
break;
default:
tempListObject.caption = list-1;
//console.write("Clicked [" + list + "," + tab + "]: list of type " + SN_STATUS_PANE_COMMAND[list][tab].type + "; " + SN_STATUS_PANE_COMMAND[list][tab].last + "; " + SN_STATUS_PANE_COMMAND[list][tab].name);
console.write("Clicked [" + list + "," + tab + "]: list of type " + SN_STATUS_PANE_COMMAND[list][tab].type + "; " + SN_STATUS_PANE_COMMAND[list][tab].name);
attempt_add_to_build_queue( selection[0], selection[0].traits.id.civ_code + "_" + SN_STATUS_PANE_COMMAND[list][tab].name );
break;
}
}

View File

@ -28,6 +28,7 @@ CObjectEntry::CObjectEntry(int type, CObjectBase* base)
m_DeathAnim=0;
m_CorpseAnim=0;
m_MeleeAnim=0;
m_GatherAnim=0;
m_RangedAnim=0;
}
@ -156,6 +157,9 @@ bool CObjectEntry::BuildRandomVariant(CObjectBase::variation_key& vars, CObjectB
if (AnimNameLC == "attack")
m_MeleeAnim = m_Animations[t].m_AnimData;
else
if (AnimNameLC == "chop")
m_GatherAnim = m_Animations[t].m_AnimData;
else
if (AnimNameLC == "death")
m_DeathAnim = m_Animations[t].m_AnimData;
else

View File

@ -35,6 +35,7 @@ public:
CSkeletonAnim* m_WalkAnim;
CSkeletonAnim* m_DeathAnim;
CSkeletonAnim* m_MeleeAnim;
CSkeletonAnim* m_GatherAnim;
CSkeletonAnim* m_RangedAnim;
CSkeletonAnim* m_CorpseAnim;

View File

@ -330,7 +330,7 @@ void CGUI::Initialize()
AddObjectType("progressbar", &CProgressBar::ConstructObject);
AddObjectType("minimap", &CMiniMap::ConstructObject);
AddObjectType("input", &CInput::ConstructObject);
AddObjectType("list", &CList::ConstructObject);
// AddObjectType("list", &CList::ConstructObject);
}
void CGUI::Process()

View File

@ -772,11 +772,13 @@ TIMER(InitScripting)
CDamageType::ScriptingInit();
CJSPropertyAccessor<CEntity>::ScriptingInit(); // <-- Doesn't really matter which we use, but we know CJSPropertyAccessor<T> is already being compiled for T = CEntity.
CScriptEvent::ScriptingInit();
CJSProgressTimer::ScriptingInit();
g_ScriptingHost.DefineConstant( "ORDER_NONE", -1 );
g_ScriptingHost.DefineConstant( "ORDER_GOTO", CEntityOrder::ORDER_GOTO );
g_ScriptingHost.DefineConstant( "ORDER_PATROL", CEntityOrder::ORDER_PATROL );
g_ScriptingHost.DefineConstant( "ORDER_ATTACK", CEntityOrder::ORDER_ATTACK_MELEE );
g_ScriptingHost.DefineConstant( "ORDER_GATHER", CEntityOrder::ORDER_GATHER );
JSI_Camera::init();
JSI_Console::init();

View File

@ -135,6 +135,9 @@ void CSelectedEntities::renderOverlays()
case CEntityOrder::ORDER_ATTACK_MELEE:
glwprintf( L"Attack" );
break;
case CEntityOrder::ORDER_GATHER:
glwprintf( L"Gather" );
break;
}
glDisable( GL_TEXTURE_2D );
@ -392,18 +395,6 @@ void CSelectedEntities::update()
m_selectionChanged = false;
g_Mouseover.m_targetChanged = false;
}
/*
if( !isContextValid( m_contextOrder ) )
{
// This order isn't valid for the current selection and/or target.
for( int t = 0; t < CEntityOrder::ORDER_LAST; t++ )
if( isContextValid( t ) )
{
m_contextOrder = t; return;
}
m_contextOrder = -1;
}
*/
if( ( m_group_highlight != -1 ) && getGroupCount( m_group_highlight ) )
g_Game->GetView()->SetCameraTarget( getGroupPosition( m_group_highlight ) );
@ -504,6 +495,18 @@ void CSelectedEntities::contextOrder( bool pushQueue )
}
return;
}
case CEntityOrder::ORDER_GATHER:
{
context.m_data[0].entity = g_Mouseover.m_target;
for( it = m_selected.begin(); it < m_selected.end(); it++ )
if( (*it)->acceptsOrder( m_contextOrder, g_Mouseover.m_target ) )
{
if( !pushQueue )
(*it)->clearOrders();
(*it)->pushOrder( context );
}
return;
}
default:
break;
}

View File

@ -192,6 +192,10 @@ CProfileManager::CProfileManager()
CProfileManager::~CProfileManager()
{
std::map<CStr8, const char*>::iterator it;
for( it = m_internedStrings.begin(); it != m_internedStrings.end(); it++ )
delete[]( it->second );
delete( root );
}
@ -209,6 +213,20 @@ void CProfileManager::StartScript( const char* name )
current->Call();
}
const char* CProfileManager::InternString( CStr8 intern )
{
std::map<CStr8, const char*>::iterator it = m_internedStrings.find( intern );
if( it != m_internedStrings.end() )
return( it->second );
size_t length = intern.length();
char* data = new char[length + 1];
strcpy( data, intern.c_str() );
data[length] = 0;
m_internedStrings.insert( std::pair<CStr8, const char*>( intern, data ) );
return( data );
}
void CProfileManager::Stop()
{
if( current->Return() )

View File

@ -99,6 +99,7 @@ class CProfileManager : public Singleton<CProfileManager>
CProfileNode* current;
double start;
double frame_start;
std::map<CStr8, const char*> m_internedStrings;
public:
CProfileManager();
@ -118,6 +119,8 @@ public:
// Resets absolutely everything
void StructuralReset();
const char* InternString( CStr8 intern );
inline const CProfileNode* GetCurrent() { return( current ); }
inline const CProfileNode* GetRoot() { return( root ); }
double GetTime();
@ -128,6 +131,7 @@ public:
class CProfileSample
{
static std::map<CStrW, char*> evMap;
public:
CProfileSample( const char* name )
{

View File

@ -5,7 +5,7 @@
#include "res/unifont.h"
#include "Hotkey.h"
bool profileVisible = true;
bool profileVisible = false;
extern int g_xres, g_yres;
const CProfileNode* currentNode = NULL;

View File

@ -90,7 +90,7 @@ JSBool JSI_Console::writeConsole( JSContext* UNUSEDPARAM(context), JSObject* UNU
{
try
{
CStrW arg = g_ScriptingHost.ValueToUCString( argv[0] );
CStrW arg = g_ScriptingHost.ValueToUCString( argv[i] );
output += arg;
}
catch( PSERROR_Scripting_ConversionFailed )

View File

@ -109,8 +109,8 @@ public:
// Check for a property
virtual IJSProperty* HasProperty( CStrW PropertyName ) = 0;
// Retrieve the value of a property
virtual void GetProperty( JSContext* cx, CStrW PropertyName, jsval* vp ) = 0;
// Retrieve the value of a property (returning false if that property is not defined)
virtual bool GetProperty( JSContext* cx, CStrW PropertyName, jsval* vp ) = 0;
// Add a property (with immediate value)
virtual void AddProperty( CStrW PropertyName, jsval Value ) = 0;
@ -435,9 +435,12 @@ template<typename T, bool ReadOnly> class CJSObject : public IJSObject
typedef STL_HASH_MAP<CStrW, CJSReflector*, CStrW_hash_compare> ReflectorTable;
JSObject* m_JS;
ReflectorTable m_Reflectors;
public:
static JSClass JSI_class;
// Whether native code is responsible for managing this object.
// Script constructors should clear this *BEFORE* creating a JS
// mirror (otherwise it'll be rooted).
@ -445,7 +448,7 @@ public:
bool m_EngineOwned;
// JS Property access
void GetProperty( JSContext* cx, CStrW PropertyName, jsval* vp );
bool GetProperty( JSContext* cx, CStrW PropertyName, jsval* vp );
void SetProperty( JSContext* cx, CStrW PropertyName, jsval* vp )
{
if( !ReadOnly )
@ -499,7 +502,8 @@ public:
CStrW PropName = g_ScriptingHost.ValueToUCString( id );
Instance->GetProperty( cx, PropName, vp );
if( !Instance->GetProperty( cx, PropName, vp ) )
return( JS_TRUE );
return( JS_TRUE );
}
@ -528,11 +532,13 @@ public:
g_ScriptingHost.DefineCustomObjectType( &JSI_class, Constructor, ConstructorMinArgs, JSI_props, JSI_methods, NULL, NULL );
delete[]( JSI_methods );
atexit( ScriptingShutdown );
}
static void ScriptingShutdown()
{
PropertyTable::iterator it;
for( it = m_SharedProperties.begin(); it != m_SharedProperties.end(); it++ )
for( it = m_IntrinsicProperties.begin(); it != m_IntrinsicProperties.end(); it++ )
delete( it->second );
}
static void DefaultFinalize( JSContext *cx, JSObject *obj )
@ -546,7 +552,7 @@ public:
}
public:
static JSClass JSI_class;
JSObject* GetScript()
{
if( !m_JS )
@ -777,7 +783,7 @@ template<typename T, bool ReadOnly> JSPropertySpec CJSObject<T, ReadOnly>::JSI_p
template<typename T, bool ReadOnly> std::vector<JSFunctionSpec> CJSObject<T, ReadOnly>::m_Methods;
template<typename T, bool ReadOnly> typename CJSObject<typename T, ReadOnly>::PropertyTable CJSObject<T, ReadOnly>::m_IntrinsicProperties;
template<typename T, bool ReadOnly> void CJSObject<T, ReadOnly>::GetProperty( JSContext* cx, CStrW PropertyName, jsval* vp )
template<typename T, bool ReadOnly> bool CJSObject<T, ReadOnly>::GetProperty( JSContext* cx, CStrW PropertyName, jsval* vp )
{
IJSProperty* Property = HasProperty( PropertyName );
if( Property && Property->m_Intrinsic )
@ -811,7 +817,7 @@ template<typename T, bool ReadOnly> void CJSObject<T, ReadOnly>::GetProperty( JS
}
if( !check )
return;
return( false );
// FIXME: Fiddle a way so this /doesn't/ require multiple kilobytes
// of memory. Can't think of any better way to do it yet. Problem is
@ -839,6 +845,7 @@ template<typename T, bool ReadOnly> void CJSObject<T, ReadOnly>::GetProperty( JS
*vp = OBJECT_TO_JSVAL( it->second->m_JSAccessor );
}
}
return( true );
}
#endif

View File

@ -98,6 +98,7 @@ public:
// TODO: Remove one of these
inline JSContext *getContext() { return m_Context; }
inline JSContext *GetContext() { return m_Context; }
inline JSObject* GetGlobalObject() { return m_GlobalObject; }
void LoadScriptFromDisk(const std::string & fileName);

View File

@ -85,6 +85,7 @@ bool CBaseEntity::loadXML( CStr filename )
AT(height);
AT(on);
AT(file);
AT(function);
#undef AT
#undef EL
@ -161,14 +162,28 @@ bool CBaseEntity::loadXML( CStr filename )
CStrW EventName = L"on" + (CStrW)Child.getAttributes().getNamedItem( at_on );
CStrW Code (Child.getText());
CStrW ExternalFunction = (CStrW)Child.getAttributes().getNamedItem( at_function );
// Does a property with this name already exist?
for( uint eventID = 0; eventID < EVENT_LAST; eventID++ )
{
if( CStrW( EventNames[eventID] ) == EventName )
{
m_EventHandlers[eventID].Compile( CStrW( filename ) + L"::" + EventName + L" (" + CStrW( Child.getLineNumber() ) + L")", Code );
if( ExternalFunction != CStrW() )
{
jsval fnval;
assert( JS_TRUE == JS_GetUCProperty( g_ScriptingHost.GetContext(), g_ScriptingHost.GetGlobalObject(), ExternalFunction.c_str(), ExternalFunction.Length(), &fnval ) );
JSFunction* fn = JS_ValueToFunction( g_ScriptingHost.GetContext(), fnval );
if( !fn )
{
LOG( ERROR, LOG_CATEGORY, "CBaseEntity::LoadXML: Function does not exist for event %hs in file %s. Load failed.", EventName.c_str(), filename.c_str() );
break;
}
m_EventHandlers[eventID].SetFunction( fn );
}
else
m_EventHandlers[eventID].Compile( CStrW( filename ) + L"::" + EventName + L" (" + CStrW( Child.getLineNumber() ) + L")", Code );
HasProperty( EventName )->m_Inherited = false;
break;
}

View File

@ -220,6 +220,12 @@ void CEntity::update( size_t timestep )
case CEntityOrder::ORDER_ATTACK_MELEE_NOPATHING:
if( processAttackMeleeNoPathing( current, timestep ) ) break;
return;
case CEntityOrder::ORDER_GATHER:
if( processGather( current, timestep ) ) break;
return;
case CEntityOrder::ORDER_GATHER_NOPATHING:
if( processGatherNoPathing( current, timestep ) ) break;
return;
case CEntityOrder::ORDER_GOTO:
if( processGoto( current, timestep ) ) break;
return;
@ -322,24 +328,7 @@ void CEntity::dispatch( const CMessage* msg )
bool CEntity::DispatchEvent( CScriptEvent* evt )
{
// MT: HACK. And it leaks.
static std::map<CStrW, char*> evMap;
char* data;
std::map<CStrW, char*>::iterator it = evMap.find( evt->m_Type );
if( it != evMap.end() )
{
data = it->second;
}
else
{
CStr8 short_string( evt->m_Type );
size_t length = short_string.length();
data = new char[length + 9];
strcpy( data, "script: " );
strcpy( data + 8, short_string.c_str() );
data[length + 8] = 0;
evMap.insert( std::pair<CStrW, char*>( evt->m_Type, data ) );
}
const char* data = g_Profiler.InternString( "script: " + (CStr8)evt->m_Type );
g_Profiler.StartScript( data );
bool rval = m_EventHandlers[evt->m_TypeCode].DispatchEvent( GetScript(), evt );
@ -368,19 +357,6 @@ bool CEntity::acceptsOrder( int orderType, CEntity* orderTarget )
{
CEventPrepareOrder evt( orderTarget, orderType );
return( DispatchEvent( &evt ) );
/*
// Hardcoding...
switch( orderType )
{
case CEntityOrder::ORDER_GOTO:
case CEntityOrder::ORDER_PATROL:
return( m_speed > 0.0f );
case CEntityOrder::ORDER_ATTACK_MELEE:
return( orderTarget && ( m_meleeRange > 0.0f ) );
}
return( false );
*/
}
void CEntity::repath()
@ -644,7 +620,7 @@ void CEntity::ScriptingInit()
AddMethod<bool, &CEntity::Kill>( "kill", 0 );
AddMethod<bool, &CEntity::Damage>( "damage", 1 );
AddMethod<bool, &CEntity::IsIdle>( "isIdle", 0 );
AddMethod<jsval, &CEntity::GetSpawnPoint>( "getSpawnPoint", 1 );
AddClassProperty( L"template", (CBaseEntity* CEntity::*)&CEntity::m_base, false, (NotifyFn)&CEntity::loadBase );
@ -659,7 +635,7 @@ JSBool CEntity::Construct( JSContext* cx, JSObject* obj, unsigned int argc, jsva
CBaseEntity* baseEntity = NULL;
CVector3D position;
float orientation = 0.0f;
float orientation = (float)( PI * ( (double)( rand() & 0x7fff ) / (double)0x4000 ) );
JSObject* jsBaseEntity = JSVAL_TO_OBJECT( argv[0] );
CStrW templateName;
@ -765,6 +741,7 @@ bool CEntity::Order( JSContext* cx, uintN argc, jsval* argv, bool Queued )
}
break;
case CEntityOrder::ORDER_ATTACK_MELEE:
case CEntityOrder::ORDER_GATHER:
if( argc < 1 )
{
JS_ReportError( cx, "Too few parameters" );
@ -779,6 +756,7 @@ bool CEntity::Order( JSContext* cx, uintN argc, jsval* argv, bool Queued )
}
newOrder.m_data[0].entity = target->me;
break;
default:
JS_ReportError( cx, "Invalid order type" );
return( false );
@ -860,3 +838,134 @@ bool CEntity::Kill( JSContext* cx, uintN argc, jsval* argv )
return( true );
}
jsval CEntity::GetSpawnPoint( JSContext* cx, uintN argc, jsval* argv )
{
float spawn_clearance = 2.0f;
if( argc >= 1 )
{
CBaseEntity* be = ToNative<CBaseEntity>( argv[0] );
if( be )
{
switch( be->m_bound_type )
{
case CBoundingObject::BOUND_CIRCLE: spawn_clearance = be->m_bound_circle->m_radius; break;
case CBoundingObject::BOUND_OABB: spawn_clearance = be->m_bound_box->m_radius; break;
default: assert( 0 && "No bounding information for spawned object!" );
}
}
else
spawn_clearance = ToPrimitive<float>( argv[0] );
}
else
assert( 0 && "No arguments to Entity::GetSpawnPoint()" );
// TODO: Make netsafe.
CBoundingCircle spawn( 0.0f, 0.0f, spawn_clearance );
if( m_bounds->m_type == CBoundingObject::BOUND_OABB )
{
CBoundingBox* oabb = (CBoundingBox*)m_bounds;
// Pick a start point
int edge = rand() & 3; int point;
double max_w = oabb->m_w + spawn_clearance + 1.0;
double max_h = oabb->m_h + spawn_clearance + 1.0;
int w_count = (int)( max_w * 2 );
int h_count = (int)( max_h * 2 );
CVector2D w_step = oabb->m_v * (float)( max_w / w_count );
CVector2D h_step = oabb->m_u * (float)( max_h / h_count );
CVector2D pos( m_position );
if( edge & 1 )
{
point = rand() % ( 2 * h_count ) - h_count;
pos += ( oabb->m_v * (float)max_w + h_step * (float)point ) * ( ( edge & 2 ) ? -1.0f : 1.0f );
}
else
{
point = rand() % ( 2 * w_count ) - w_count;
pos += ( oabb->m_u * (float)max_h + w_step * (float)point ) * ( ( edge & 2 ) ? -1.0f : 1.0f );
}
int start_edge = edge; int start_point = point;
spawn.m_pos = pos;
// Then step around the edge (clockwise) until a free space is found, or
// we've gone all the way around.
while( getCollisionObject( &spawn ) )
{
switch( edge )
{
case 0:
point++; pos += w_step;
if( point >= w_count )
{
edge = 1;
point = -h_count;
}
break;
case 1:
point++; pos -= h_step;
if( point >= h_count )
{
edge = 2;
point = w_count;
}
break;
case 2:
point--; pos -= w_step;
if( point <= -w_count )
{
edge = 3;
point = h_count;
}
break;
case 3:
point--; pos += h_step;
if( point <= -h_count )
{
edge = 0;
point = -w_count;
}
break;
}
if( ( point == start_point ) && ( edge == start_edge ) )
return( JSVAL_NULL );
spawn.m_pos = pos;
}
CVector3D rval( pos.x, g_Game->GetWorld()->GetTerrain()->getExactGroundLevel( pos.x, pos.y ), pos.y );
return( ToJSVal( rval ) );
}
else if( m_bounds->m_type == CBoundingObject::BOUND_CIRCLE )
{
float ang;
ang = (float)( rand() & 0x7fff ) / (float)0x4000; /* 0...2 */
ang *= PI;
float radius = m_bounds->m_radius + 1.0f + spawn_clearance;
float d_ang = spawn_clearance / ( 2.0f * radius );
float ang_end = ang + 2.0f * PI;
float x, y;
for( ; ang < ang_end; ang += d_ang )
{
x = m_position.X + radius * cos( ang );
y = m_position.Z + radius * sin( ang );
spawn.setPosition( x, y );
if( !getCollisionObject( &spawn ) )
break;
}
if( ang < ang_end )
{
// Found a satisfactory position...
CVector3D pos( x, 0, y );
pos.Y = g_Game->GetWorld()->GetTerrain()->getExactGroundLevel( x, y );
return( ToJSVal( pos ) );
}
else
return( JSVAL_NULL );
}
return( JSVAL_NULL );
}

View File

@ -43,7 +43,7 @@
#include "EntityMessage.h"
#include "EventHandlers.h"
#include "ScriptObject.h"
#include "ObjectEntry.h"
#include "EntitySupport.h"
class CBaseEntity;
@ -124,8 +124,15 @@ private:
CEntity( CBaseEntity* base, CVector3D position, float orientation );
/*EGotoSituation*/ uint processGotoHelper( CEntityOrder* current, size_t timestep_milli, HEntity& collide );
bool processContactAction( CEntityOrder* current, size_t timestep_millis, int transition, float range );
bool processContactActionNoPathing( CEntityOrder* current, size_t timestep_millis, CSkeletonAnim* animation, CScriptEvent* contactEvent, float range, float minRange );
bool processAttackMelee( CEntityOrder* current, size_t timestep_milli );
bool processAttackMeleeNoPathing( CEntityOrder* current, size_t timestep_milli );
bool processGather( CEntityOrder* current, size_t timestep_milli );
bool processGatherNoPathing( CEntityOrder* current, size_t timestep_milli );
bool processGotoNoPathing( CEntityOrder* current, size_t timestep_milli );
bool processGoto( CEntityOrder* current, size_t timestep_milli );
bool processPatrol( CEntityOrder* current, size_t timestep_milli );
@ -204,6 +211,7 @@ public:
}
bool Damage( JSContext* cx, uintN argc, jsval* argv );
bool Kill( JSContext* cx, uintN argc, jsval* argv );
jsval GetSpawnPoint( JSContext* cx, uintN argc, jsval* argv );
bool IsIdle( JSContext* cx, uintN argc, jsval* argv )
{
return( m_orderQueue.empty() );

View File

@ -58,6 +58,8 @@ public:
ORDER_PATROL,
ORDER_ATTACK_MELEE,
ORDER_ATTACK_MELEE_NOPATHING,
ORDER_GATHER,
ORDER_GATHER_NOPATHING,
ORDER_PATH_END_MARKER,
ORDER_LAST
} m_type;

View File

@ -268,7 +268,8 @@ bool CEntity::processGotoNoPathing( CEntityOrder* current, size_t timestep_milli
}
}
bool CEntity::processAttackMelee( CEntityOrder* current, size_t timestep_millis )
// Handles processing common to (at the moment) gather and melee attack actions
bool CEntity::processContactAction( CEntityOrder* current, size_t timestep_millis, int transition, float range )
{
m_orderQueue.pop_front();
@ -279,7 +280,7 @@ bool CEntity::processAttackMelee( CEntityOrder* current, size_t timestep_millis
if( ( current->m_data[0].location - m_position ).length() < m_meleeRange )
{
current->m_type = CEntityOrder::ORDER_ATTACK_MELEE_NOPATHING;
(int&)current->m_type = transition;
return( true );
}
@ -292,14 +293,13 @@ bool CEntity::processAttackMelee( CEntityOrder* current, size_t timestep_millis
// The pathfinder will push its result back into this unit's queue.
g_Pathfinder.requestMeleeAttackPath( me, current->m_data[0].entity );
g_Pathfinder.requestContactPath( me, current->m_data[0].entity, transition );
return( true );
}
bool CEntity::processAttackMeleeNoPathing( CEntityOrder* current, size_t timestep_millis )
bool CEntity::processContactActionNoPathing( CEntityOrder* current, size_t timestep_millis, CSkeletonAnim* animation, CScriptEvent* contactEvent, float range, float minRange = 0.0f )
{
// Target's dead? Then our work here is done.
// Target's dead (or exhausted)? Then our work here is done.
if( !current->m_data[0].entity || !current->m_data[0].entity->m_extant )
{
m_orderQueue.pop_front();
@ -309,7 +309,7 @@ bool CEntity::processAttackMeleeNoPathing( CEntityOrder* current, size_t timeste
if( m_actor )
{
// Still playing attack animation? Suspend processing.
if( m_actor->GetModel()->GetAnimation() == m_actor->GetObject()->m_MeleeAnim )
if( m_actor->GetModel()->GetAnimation() == animation )
return( false );
// Just transitioned? No animation? (=> melee just finished) Play walk.
@ -319,13 +319,16 @@ bool CEntity::processAttackMeleeNoPathing( CEntityOrder* current, size_t timeste
CVector2D delta = current->m_data[0].entity->m_position - m_position;
float adjRange = m_meleeRange + m_bounds->m_radius + current->m_data[0].entity->m_bounds->m_radius;
float adjMinRange = m_meleeRangeMin + m_bounds->m_radius + current->m_data[0].entity->m_bounds->m_radius;
float adjRange = range + m_bounds->m_radius + current->m_data[0].entity->m_bounds->m_radius;
if( delta.within( adjMinRange ) )
if( minRange > 0.0f )
{
// Too close... do nothing.
return( false );
float adjMinRange = m_meleeRangeMin + m_bounds->m_radius + current->m_data[0].entity->m_bounds->m_radius;
if( delta.within( adjMinRange ) )
{
// Too close... do nothing.
return( false );
}
}
if( !delta.within( adjRange ) )
@ -393,17 +396,32 @@ bool CEntity::processAttackMeleeNoPathing( CEntityOrder* current, size_t timeste
m_ahead = delta.normalize();
}
// Now we've got this far:
// Pointy end goes into the other man...
CEventAttack AttackEvent( current->m_data[0].entity );
if( DispatchEvent( &AttackEvent ) && m_actor )
m_actor->GetModel()->SetAnimation( m_actor->GetObject()->m_MeleeAnim, true );
if( DispatchEvent( contactEvent ) && m_actor )
m_actor->GetModel()->SetAnimation( animation, true );
return( false );
}
bool CEntity::processAttackMelee( CEntityOrder* current, size_t timestep_millis )
{
return( processContactAction( current, timestep_millis, CEntityOrder::ORDER_ATTACK_MELEE_NOPATHING, 0.5 ) );
}
bool CEntity::processAttackMeleeNoPathing( CEntityOrder* current, size_t timestep_milli )
{
CEventAttack evt( current->m_data[0].entity );
return( processContactActionNoPathing( current, timestep_milli, m_actor ? m_actor->GetObject()->m_MeleeAnim : NULL, &evt, m_meleeRange, m_meleeRangeMin ) );
}
bool CEntity::processGather( CEntityOrder* current, size_t timestep_millis )
{
return( processContactAction( current, timestep_millis, CEntityOrder::ORDER_GATHER_NOPATHING, 0.5 ) );
}
bool CEntity::processGatherNoPathing( CEntityOrder* current, size_t timestep_millis )
{
CEventGather evt( current->m_data[0].entity );
return( processContactActionNoPathing( current, timestep_millis, m_actor ? m_actor->GetObject()->m_GatherAnim : NULL, &evt, 5.0, 0.0 ) );
}
bool CEntity::processGoto( CEntityOrder* current, size_t timestep_millis )
{
float timestep=timestep_millis/1000.0f;

View File

@ -8,6 +8,12 @@ CEventAttack::CEventAttack( CEntity* target ) : CScriptEvent( L"attack", true, E
AddProperty( L"target", &m_target );
}
CEventGather::CEventGather( CEntity* target ) : CScriptEvent( L"gather", true, EVENT_GATHER )
{
m_target = target;
AddProperty( L"target", &m_target );
}
CEventDamage::CEventDamage( CEntity* inflictor, CDamageType* damage ) : CScriptEvent( L"takesDamage", true, EVENT_DAMAGE )
{
m_inflictor = inflictor;

View File

@ -15,6 +15,7 @@ enum EEventType
EVENT_INITIALIZE,
EVENT_TICK,
EVENT_ATTACK,
EVENT_GATHER,
EVENT_DAMAGE,
EVENT_TARGET_CHANGED,
EVENT_PREPARE_ORDER,
@ -27,6 +28,7 @@ static const wchar_t* EventNames[] =
/* EVENT_INITIALIZE */ L"onInitialize",
/* EVENT_TICK */ L"onTick",
/* EVENT_ATTACK */ L"onAttack", /* This unit is the one doing the attacking... */
/* EVENT_GATHER */ L"onGather", /* This unit is the one doing the gathering... */
/* EVENT_DAMAGE */ L"onTakesDamage",
/* EVENT_TARGET_CHANGED */ L"onTargetChanged", /* If this unit is selected and the mouseover object changes */
/* EVENT_PREPARE_ORDER */ L"onPrepareOrder", /* To check if a unit can execute a given order */
@ -52,6 +54,13 @@ public:
CEventAttack( CEntity* target );
};
class CEventGather : public CScriptEvent
{
CEntity* m_target;
public:
CEventGather( CEntity* target );
};
class CEventDamage : public CScriptEvent
{
CEntity* m_inflictor;

View File

@ -16,7 +16,7 @@ void CPathfindEngine::requestPath( HEntity entity, const CVector2D& destination
pathSparse( entity, destination );
}
void CPathfindEngine::requestMeleeAttackPath( HEntity entity, HEntity target )
void CPathfindEngine::requestContactPath( HEntity entity, HEntity target, int transition )
{
pathSparse( entity, target->m_position );
// For attack orders, do some additional postprocessing (replace goto/nopathing
@ -28,7 +28,7 @@ void CPathfindEngine::requestMeleeAttackPath( HEntity entity, HEntity target )
break;
if( it->m_type == CEntityOrder::ORDER_GOTO_NOPATHING )
{
it->m_type = CEntityOrder::ORDER_ATTACK_MELEE_NOPATHING;
(int&)it->m_type = transition;
it->m_data[0].entity = target;
}
}

View File

@ -28,7 +28,7 @@ class CPathfindEngine : public Singleton<CPathfindEngine>
public:
CPathfindEngine();
void requestPath( HEntity entity, const CVector2D& destination );
void requestMeleeAttackPath( HEntity entity, HEntity target );
void requestContactPath( HEntity entity, HEntity target, int transition );
};
#endif

View File

@ -47,10 +47,16 @@ void CScheduler::pushInterval( size_t first, size_t interval, JSFunction* functi
timeFunction.push( SDispatchObjectFunction( function, simulationTime + first, operateOn, interval ) );
}
void CScheduler::pushProgressTimer( CJSProgressTimer* progressTimer )
{
progressTimers.push_back( progressTimer );
}
void CScheduler::update(size_t simElapsed)
{
simulationTime += simElapsed;
frameCount++;
jsval rval;
while( !timeScript.empty() )
{
@ -77,7 +83,6 @@ void CScheduler::update(size_t simElapsed)
if( top.deliveryTime > simulationTime )
break;
timeFunction.pop();
jsval rval;
m_abortInterval = false;
JS_CallFunction( g_ScriptingHost.getContext(), top.operateOn, top.function, 0, NULL, &rval );
@ -91,9 +96,66 @@ void CScheduler::update(size_t simElapsed)
if( top.deliveryTime > frameCount )
break;
frameFunction.pop();
jsval rval;
JS_CallFunction( g_ScriptingHost.getContext(), top.operateOn, top.function, 0, NULL, &rval );
}
std::list<CJSProgressTimer*>::iterator it;
for( it = progressTimers.begin(); it != progressTimers.end(); it++ )
{
(*it)->m_Current += (*it)->m_Increment * simElapsed;
if( (*it)->m_Current >= (*it)->m_Max )
{
(*it)->m_Current = (*it)->m_Max;
if( (*it)->m_Callback )
JS_CallFunction( g_ScriptingHost.GetContext(), (*it)->m_OperateOn, (*it)->m_Callback, 0, NULL, &rval );
it = progressTimers.erase( it );
}
}
}
CJSProgressTimer::CJSProgressTimer( double Max, double Increment, JSFunction* Callback, JSObject* OperateOn )
{
m_Max = Max; m_Increment = Increment; m_Callback = Callback; m_OperateOn = OperateOn; m_Current = 0.0;
}
void CJSProgressTimer::ScriptingInit()
{
AddClassProperty( L"max", &CJSProgressTimer::m_Max );
AddClassProperty( L"current", &CJSProgressTimer::m_Current );
AddClassProperty( L"increment", &CJSProgressTimer::m_Increment );
CJSObject<CJSProgressTimer>::ScriptingInit( "ProgressTimer", Construct, 2 );
}
JSBool CJSProgressTimer::Construct( JSContext* cx, JSObject* obj, unsigned int argc, jsval* argv, jsval* rval )
{
assert( argc >= 2 );
double max = ToPrimitive<double>( argv[0] );
double increment = ToPrimitive<double>( argv[1] );
JSFunction* callback_fn = NULL;
JSObject* scope_obj = NULL;
if( argc >= 3 )
{
callback_fn = JS_ValueToFunction( cx, argv[2] );
if( ( argc >= 4 ) && JSVAL_IS_OBJECT( argv[3] ) )
{
scope_obj = JSVAL_TO_OBJECT( argv[3] );
}
else
{
// Attempt to determine object to operate on automatically.
// SpiderMonkey docs say the 'this' parameter of the calling
// function is in argv[-2]. Do I believe them? One way to find out.
scope_obj = JSVAL_TO_OBJECT( argv[-2] );
}
}
CJSProgressTimer* timer = new CJSProgressTimer( max, increment, callback_fn, scope_obj );
timer->m_EngineOwned = false;
g_Scheduler.pushProgressTimer( timer );
*rval = OBJECT_TO_JSVAL( timer->GetScript() );
return( JS_TRUE );
}

View File

@ -13,7 +13,9 @@
#include "EntityHandles.h"
#include "Singleton.h"
#include "CStr.h"
#include "scripting/ScriptingHost.h"
#include "scripting/ScriptableObject.h"
class CJSProgressTimer;
// Message, destination and delivery time information.
struct SDispatchObject
@ -30,19 +32,6 @@ struct SDispatchObject
}
};
/*
struct SDispatchObjectMessage : public SDispatchObject
{
HEntity destination;
const CMessage* message;
inline SDispatchObjectMessage( const HEntity& _destination, size_t _deliveryTime, const CMessage* _message )
: SDispatchObject( _deliveryTime ), destination( _destination ), message( _message ) {}
inline SDispatchObjectMessage( const HEntity& _destination, size_t _deliveryTime, const CMessage* _message, const size_t recurrence )
: SDispatchObject( _deliveryTime, recurrence ), destination( _destination ), message( _message ) {}
};
*/
struct SDispatchObjectScript : public SDispatchObject
{
CStrW script;
@ -65,28 +54,35 @@ struct SDispatchObjectFunction : public SDispatchObject
struct CScheduler : public Singleton<CScheduler>
{
// std::priority_queue<SDispatchObjectMessage> timeMessage, frameMessage;
std::priority_queue<SDispatchObjectScript> timeScript, frameScript;
std::priority_queue<SDispatchObjectFunction> timeFunction, frameFunction;
std::list<CJSProgressTimer*> progressTimers;
bool m_abortInterval;
/*
void pushTime( size_t delay, const HEntity& destination, const CMessage* message );
void pushFrame( size_t delay, const HEntity& destination, const CMessage* message );
*/
void pushTime( size_t delay, const CStrW& fragment, JSObject* operateOn = NULL );
void pushFrame( size_t delay, const CStrW& fragment, JSObject* operateOn = NULL );
void pushInterval( size_t first, size_t interval, const CStrW& fragment, JSObject* operateOn = NULL );
void pushTime( size_t delay, JSFunction* function, JSObject* operateOn = NULL );
void pushFrame( size_t delay, JSFunction* function, JSObject* operateOn = NULL );
void pushInterval( size_t first, size_t interval, JSFunction* function, JSObject* operateOn = NULL );
void pushProgressTimer( CJSProgressTimer* progressTimer );
void update(size_t elapsedSimulationTime);
};
#define g_Scheduler CScheduler::GetSingleton()
class CJSProgressTimer : public CJSObject<CJSProgressTimer>
{
friend CScheduler;
double m_Max, m_Current, m_Increment;
JSFunction* m_Callback;
JSObject* m_OperateOn;
CJSProgressTimer( double Max, double Increment, JSFunction* Callback, JSObject* OperateOn );
static JSBool Construct( JSContext* cx, JSObject* obj, unsigned int argc, jsval* argv, jsval* rval );
public:
static void ScriptingInit();
};
extern const int ORDER_DELAY;
#endif