#include #include "types.h" #include "fastmath.h" #include "memory.h" #include "debug.h" #include "vector.h" #include "spaceobj.h" #include "aitrack.h" #include "aiship.h" #include "rescollect.h" #include "commandlayer.h" #include "statscript.h" #include "univupdate.h" #include "universe.h" #include "soundevent.h" #include "collision.h" #include "physics.h" #include "tutor.h" #include "randy.h" #include "clouds.h" #include "nebulae.h" #include "singleplayer.h" #ifndef HW_Release #ifdef gshaw #define DEBUG_COLLECTRESOURCES #endif #endif /*============================================================================= Tweakables: =============================================================================*/ real32 COLLECTRESOURCE_DIST = 400.0f; real32 MAX_RESOURCE_VELOCITY_TO_CHASE = 50.0f; real32 MAX_RESOURCE_VELOCITY_TO_CHASE_LEVEL6 = 50.0f; real32 ASTEROID_HARVEST_RANGE = 400.0f; real32 ASTEROID_MAXHARVEST_RANGE = 600.0f; udword CIRCLE_HARVEST_ASTEROIDS_BIGGEREQUAL_THAN = 2; real32 CIRCLE_RIGHT_VELOCITY = 20.0f; real32 CIRCLE_RIGHT_THRUST = 0.1f; real32 DUSTCLOUD_RU_DAMAGE_RATIO; real32 ASTEROID_RU_DAMAGE_RATIO[NUM_ASTEROIDTYPES] = { 0.5f, 0.5f, 0.5f, 0.5f, 0.5f }; real32 ASTEROIDBREAK_LO_VELOCITY = -200.0f; real32 ASTEROIDBREAK_HI_VELOCITY = 200.0f; real32 ASTEROIDBREAK_RADIUS_SCALE = 0.7f; real32 ASTEROIDBREAK_LO_ROTATE = -1.0f; real32 ASTEROIDBREAK_HI_ROTATE = 1.0f; real32 ASTEROID_LO_ROTATE[NUM_ASTEROIDTYPES] = { -1.0f, -1.0f, -1.0f, -1.0f, -1.0f }; real32 ASTEROID_HI_ROTATE[NUM_ASTEROIDTYPES] = { 1.0f , 1.0f , 1.0f , 1.0f , 1.0f }; udword ASTEROID_ROTATE_PROB = 150; real32 DUSTCLOUD_RCOLLECTOR_BLOWUP_SCALE = 1.0f; real32 COLLECTRESOURCE_ZONE = 2000.0f; // regrow resources stuff udword REGROW_RESOURCES_CHECK_RATE = 255; udword REGROW_RESOURCES_CHECK_FRAME = 86; real32 REGROW_RATE_MODIFIER[REGROWRATE_Max+1] = { 0.25f, 0.5f, 0.75f, 1.0f, 1.25f, 1.5f, 1.75f, 2.0f }; sdword regenerateRUrate = 255; sdword regenerateRUsIfUnder = 400; sdword regenerateRUs = 25; udword CHECK_HARV_OUTOFWORLD_RESOURCE_RATE = 127; udword CHECK_SHIP_OUTOFWORLD_RATE = 127; scriptEntry ResCollectTweaks[] = { makeEntry(COLLECTRESOURCE_DIST,scriptSetReal32CB), makeEntry(MAX_RESOURCE_VELOCITY_TO_CHASE,scriptSetReal32CB), makeEntry(MAX_RESOURCE_VELOCITY_TO_CHASE_LEVEL6,scriptSetReal32CB), makeEntry(ASTEROID_HARVEST_RANGE,scriptSetReal32CB), makeEntry(ASTEROID_MAXHARVEST_RANGE,scriptSetReal32CB), makeEntry(CIRCLE_HARVEST_ASTEROIDS_BIGGEREQUAL_THAN,scriptSetUdwordCB), makeEntry(CIRCLE_RIGHT_VELOCITY,scriptSetReal32CB), makeEntry(CIRCLE_RIGHT_THRUST,scriptSetReal32CB), makeEntry(ASTEROID_RU_DAMAGE_RATIO[Asteroid0],scriptSetReal32CB), makeEntry(ASTEROID_RU_DAMAGE_RATIO[Asteroid1],scriptSetReal32CB), makeEntry(ASTEROID_RU_DAMAGE_RATIO[Asteroid2],scriptSetReal32CB), makeEntry(ASTEROID_RU_DAMAGE_RATIO[Asteroid3],scriptSetReal32CB), makeEntry(ASTEROID_RU_DAMAGE_RATIO[Asteroid4],scriptSetReal32CB), makeEntry(DUSTCLOUD_RU_DAMAGE_RATIO,scriptSetReal32CB), makeEntry(ASTEROIDBREAK_LO_VELOCITY,scriptSetReal32CB), makeEntry(ASTEROIDBREAK_HI_VELOCITY,scriptSetReal32CB), makeEntry(ASTEROIDBREAK_LO_ROTATE,scriptSetReal32CB), makeEntry(ASTEROIDBREAK_HI_ROTATE,scriptSetReal32CB), makeEntry(ASTEROIDBREAK_RADIUS_SCALE,scriptSetReal32CB), makeEntry(ASTEROID_LO_ROTATE[Asteroid0],scriptSetReal32CB), makeEntry(ASTEROID_HI_ROTATE[Asteroid0],scriptSetReal32CB), makeEntry(ASTEROID_LO_ROTATE[Asteroid1],scriptSetReal32CB), makeEntry(ASTEROID_HI_ROTATE[Asteroid1],scriptSetReal32CB), makeEntry(ASTEROID_LO_ROTATE[Asteroid2],scriptSetReal32CB), makeEntry(ASTEROID_HI_ROTATE[Asteroid2],scriptSetReal32CB), makeEntry(ASTEROID_LO_ROTATE[Asteroid3],scriptSetReal32CB), makeEntry(ASTEROID_HI_ROTATE[Asteroid3],scriptSetReal32CB), makeEntry(ASTEROID_LO_ROTATE[Asteroid4],scriptSetReal32CB), makeEntry(ASTEROID_HI_ROTATE[Asteroid4],scriptSetReal32CB), makeEntry(ASTEROID_ROTATE_PROB,scriptSetUdwordCB), makeEntry(DUSTCLOUD_RCOLLECTOR_BLOWUP_SCALE,scriptSetReal32CB), makeEntry(COLLECTRESOURCE_ZONE,scriptSetReal32CB), makeEntry(REGROW_RESOURCES_CHECK_RATE,scriptSetUdwordCB), makeEntry(REGROW_RESOURCES_CHECK_FRAME,scriptSetUdwordCB), makeEntry(REGROW_RATE_MODIFIER[0],scriptSetReal32CB), makeEntry(REGROW_RATE_MODIFIER[1],scriptSetReal32CB), makeEntry(REGROW_RATE_MODIFIER[2],scriptSetReal32CB), makeEntry(REGROW_RATE_MODIFIER[3],scriptSetReal32CB), makeEntry(REGROW_RATE_MODIFIER[4],scriptSetReal32CB), makeEntry(REGROW_RATE_MODIFIER[5],scriptSetReal32CB), makeEntry(REGROW_RATE_MODIFIER[6],scriptSetReal32CB), makeEntry(REGROW_RATE_MODIFIER[7],scriptSetReal32CB), makeEntry(regenerateRUrate,scriptSetSdwordCB), makeEntry(regenerateRUsIfUnder,scriptSetSdwordCB), makeEntry(regenerateRUs,scriptSetSdwordCB), makeEntry(CHECK_HARV_OUTOFWORLD_RESOURCE_RATE,scriptSetUdwordCB), makeEntry(CHECK_SHIP_OUTOFWORLD_RATE,scriptSetUdwordCB), endEntry }; sdword RESOURCE_NOT_ACCESSIBLE_SECONDS = 60; /*============================================================================= Private functions prototypes: =============================================================================*/ void InitShipForResourceCollection(Ship *ship,Resource *resource); /*============================================================================= Functions =============================================================================*/ /*----------------------------------------------------------------------------- Name : ResourceMovingTooFast Description : returns TRUE if resource is moving too fast to be harvested Inputs : resource Outputs : Return : returns TRUE if resource is moving too fast to be harvested ----------------------------------------------------------------------------*/ bool ResourceMovingTooFast(Resource *resource) { real32 maxvelocitychase = MAX_RESOURCE_VELOCITY_TO_CHASE; if (singlePlayerGame && (singlePlayerGameInfo.currentMission == 6)) { maxvelocitychase = MAX_RESOURCE_VELOCITY_TO_CHASE_LEVEL6; } if ((abs(resource->posinfo.velocity.x) > maxvelocitychase) || (abs(resource->posinfo.velocity.y) > maxvelocitychase) || (abs(resource->posinfo.velocity.z) > maxvelocitychase) ) { #ifdef DEBUG_COLLECTRESOURCES dbgMessage("\nResource moving too fast. Picking different one..."); #endif return TRUE; } return FALSE; } /*----------------------------------------------------------------------------- Name : ResourceAlreadyBeingHarvested Description : returns TRUE if resource is already being targeted for harvesting Inputs : Outputs : Return : returns TRUE if resource is already being targeted for harvesting ----------------------------------------------------------------------------*/ bool ResourceAlreadyBeingHarvested(struct CommandLayer *comlayer,struct CommandToDo *IAmThisCommand,Resource *resource) { Node *curnode = comlayer->todolist.head; ResourceStaticInfo *resourcestaticinfo = resource->staticinfo; CommandToDo *command; if (!resourcestaticinfo->harvestableByMultipleShips) { while (curnode != NULL) { command = (CommandToDo *)listGetStructOfNode(curnode); if ((command->ordertype.order == COMMAND_COLLECTRESOURCE) && (command != IAmThisCommand)) { if (command->collect.resource == resource) { #ifdef DEBUG_COLLECTRESOURCES dbgMessage("\nResource already targeted. Picking different one..."); #endif return TRUE; } } curnode = curnode->next; } return FALSE; } else { // harvestable my multiple ships, so find how many ships are targeting it udword targetedby = 0; while (curnode != NULL) { command = (CommandToDo *)listGetStructOfNode(curnode); if ((command->ordertype.order == COMMAND_COLLECTRESOURCE) && (command != IAmThisCommand)) { if (command->collect.resource == resource) { targetedby++; } } curnode = curnode->next; } if (targetedby >= resourcestaticinfo->harvestableByMultipleShips) { #ifdef DEBUG_COLLECTRESOURCES dbgMessage("\nResource being targeted by too many. Picking different one..."); #endif return TRUE; } return FALSE; } } Resource *rescollectFindNearestResource(Ship *ship,struct CommandToDo *command) { Resource *resource; if (command->collect.resourceVolumeSize) { resource = univFindNearestResource(ship,command->collect.resourceVolumeSize,&command->collect.resourceVolumePosition); if (resource == NULL) { command->collect.resourceVolumeSize = 0.0f; // no more resources in this volume, so don't keep looking in it goto novolumelook; } } else { novolumelook: resource = univFindNearestResource(ship,0,NULL); } return resource; } void ChangeSingleShipToCollectResource(struct CommandToDo *command) { Ship *ship = command->selection->ShipPtr[0]; Resource *resource; dbgAssert(command->selection->numShips == 1); resource = rescollectFindNearestResource(ship,command); InitShipForResourceCollection(ship,resource); command->ordertype.order = COMMAND_COLLECTRESOURCE; command->ordertype.attributes = 0; command->collect.resource = resource; PrepareOneShipForCommand(ship,command,TRUE); } /*----------------------------------------------------------------------------- Name : clCollectResource Description : command for a ship to collect a resource Inputs : Outputs : Return : ----------------------------------------------------------------------------*/ void clCollectResource(CommandLayer *comlayer,SelectCommand *selectcom,ResourcePtr resource) { CommandToDo *newcommand; SelectCommand *selection; sdword numShips; Ship *ship; sdword i; numShips = selectcom->numShips; if (numShips == 0) { dbgMessage("\nNo ships to get resources"); return; } #ifdef DEBUG_COLLECTRESOURCES dbgMessage("\nReceived command to harvest"); #endif RemoveShipsFromDoingStuff(comlayer,selectcom); // add COMMAND_COLLECTRESOURCE commands for each resource collector: for (i=0;iShipPtr[i]; InitShipForResourceCollection(ship,resource); newcommand = memAlloc(sizeof(CommandToDo),"ToDo",0); InitCommandToDo(newcommand); selection = memAlloc(sizeof(SelectCommand),"ToDoSelection",0); selection->numShips = 1; selection->ShipPtr[0] = ship; newcommand->selection = selection; newcommand->ordertype.order = COMMAND_COLLECTRESOURCE; newcommand->ordertype.attributes = 0; newcommand->collect.resource = resource; newcommand->collect.resourceVolumeSize = 0.0f; vecZeroVector(newcommand->collect.resourceVolumePosition); if (resource) { newcommand->collect.resourceVolumeSize = COLLECTRESOURCE_ZONE; newcommand->collect.resourceVolumePosition = resource->posinfo.position; } PrepareShipsForCommand(newcommand,TRUE); listAddNode(&comlayer->todolist,&newcommand->todonode,newcommand); } } void HandleAsteroidScaling(Asteroid *asteroid) { AsteroidStaticInfo *asteroidstatic = (AsteroidStaticInfo *)asteroid->staticinfo; if (asteroidstatic->asteroidCanShrink) { real32 oldscaling = asteroid->scaling; asteroid->scaling = (real32)asteroid->resourcevalue / (real32)asteroidstatic->resourcevalue; if (asteroid->scaling < asteroidstatic->asteroidMinShrink) { asteroid->scaling = asteroidstatic->asteroidMinShrink; } if (asteroid->scaling != oldscaling) { MakeNewAsteroidStaticInfo(asteroid); } } } void HandleDustCloudScaling(DustCloud* dustcloud) { cloudSystem* csys; DustCloudStaticInfo* dustcloudstatic = (DustCloudStaticInfo*)dustcloud->staticinfo; if (dustcloudstatic->dustcloudCanShrink) { real32 oldscaling = dustcloud->scaling; dustcloud->scaling = (real32)dustcloud->resourcevalue / (real32)dustcloudstatic->resourcevalue; if (dustcloud->scaling > 1.0f) { dustcloud->scaling = 1.0f; } if (dustcloud->scaling < dustcloudstatic->dustcloudMinShrink) { dustcloud->scaling = dustcloudstatic->dustcloudMinShrink; } if (dustcloud->scaling != oldscaling) { MakeNewDustCloudStaticInfo(dustcloud); } csys = (cloudSystem*)dustcloud->stub; if (csys != NULL) { csys->healthFactor = dustcloud->scaling; } } } void HandleGasCloudScaling(GasCloud *gascloud) { GasCloudStaticInfo *gascloudstatic = (GasCloudStaticInfo *)gascloud->staticinfo; if (gascloudstatic->gascloudCanShrink) { real32 oldscaling = gascloud->scaling; gascloud->scaling = (real32)gascloud->resourcevalue / (real32)gascloudstatic->resourcevalue; if (gascloud->scaling < gascloudstatic->gascloudMinShrink) { gascloud->scaling = gascloudstatic->gascloudMinShrink; } if (gascloud->scaling != oldscaling) { MakeNewGasCloudStaticInfo(gascloud); } } } void HandleNebulaScaling(Nebula* nebula) { NebulaStaticInfo* nebulastatic; real32 factor; nebulastatic = (NebulaStaticInfo*)nebula->staticinfo; factor = (real32)nebula->resourcevalue / (real32)nebulastatic->resourcevalue; nebFadeAttachedTendrils((nebChunk*)nebula->stub, factor); } /*----------------------------------------------------------------------------- Name : DustCloudTakesDamage Description : applies damage to dustcloud, causing it to lose RU's and possibly die and wreak havok as it does so. charging takes place here, too Inputs : asteroid, damagetaken, targetWasAlive Outputs : Return : returns TRUE if should delete dustcloud ----------------------------------------------------------------------------*/ bool DustCloudTakesDamage(DustCloud* dustcloud, sdword damagetaken, bool targetWasAlive) { sdword RUdamage; if (!targetWasAlive) { return FALSE; // target already dead, so ignore } RUdamage = (sdword) (damagetaken * DUSTCLOUD_RU_DAMAGE_RATIO); dustcloud->resourcevalue -= RUdamage; if (dustcloud->resourcevalue <= 0) { return TRUE; // all RU's have been sapped out. Just kill asteroid without splitting it up. } if (dustcloud->health <= 0) { //clouds can't break. instead they do something cool upon losing all health return TRUE; } else { HandleDustCloudScaling(dustcloud); return FALSE; } } /*----------------------------------------------------------------------------- Name : DustCloudChargesUp Description : ion cannon fire charges up a dustcloud; this fn performs the charging Inputs : dustcloud, damagetaken, targetWasAlive Outputs : Return : ----------------------------------------------------------------------------*/ void DustCloudChargesUp(DustCloud* dustcloud, sdword damagetaken, bool targetWasAlive) { DustCloudStaticInfo* dustcloudstatic = (DustCloudStaticInfo*)dustcloud->staticinfo; cloudSystem* csys = (cloudSystem*)dustcloud->stub; if (!targetWasAlive || csys == NULL) { return; } csys->charge += 0.5f * ((real32)damagetaken / (real32)csys->maxhealth); if (csys->charge > 1.0f) { bitSet(csys->flags, LIGHTNING_DISCHARGING); csys->charge = 1.0f; } } /*----------------------------------------------------------------------------- Name : BreakAsteroidUp Description : Breaks the asteroid up into little fragments Inputs : asteroid Outputs : Return : ----------------------------------------------------------------------------*/ void BreakAsteroidUp(Asteroid *asteroid) { AsteroidStaticInfo *asteroidstatic = (AsteroidStaticInfo *)asteroid->staticinfo; Asteroid *newasteroid; AsteroidType asteroidtype; udword j; udword numAsteroidsToMake; vector randpos; real32 randposrange; vector originalvelocity; vector randomvelocity; dbgAssert(asteroid->objtype == OBJ_AsteroidType); // asteroid has been destroyed. Split it up into multiple asteroids if (asteroidstatic->asteroidCanBreak) { randposrange = asteroidstatic->staticheader.staticCollInfo.collspheresize * ASTEROIDBREAK_RADIUS_SCALE; originalvelocity = asteroid->posinfo.velocity; for (asteroidtype=Asteroid0;asteroidtypebreakinto[asteroidtype]; for (j=0;jcollInfo.collPosition); newasteroid = univAddAsteroid(asteroidtype,&randpos); randomvelocity.x = frandombetween(ASTEROIDBREAK_LO_VELOCITY,ASTEROIDBREAK_HI_VELOCITY); randomvelocity.y = frandombetween(ASTEROIDBREAK_LO_VELOCITY,ASTEROIDBREAK_HI_VELOCITY); randomvelocity.z = frandombetween(ASTEROIDBREAK_LO_VELOCITY,ASTEROIDBREAK_HI_VELOCITY); vecAdd(newasteroid->posinfo.velocity,randomvelocity,originalvelocity); if (asteroid->asteroidtype >= Asteroid2) if (((udword)(gamerand() & 255)) <= ASTEROID_ROTATE_PROB) { newasteroid->rotinfo.rotspeed.x = frandombetween(ASTEROIDBREAK_LO_ROTATE,ASTEROIDBREAK_HI_ROTATE); newasteroid->rotinfo.rotspeed.y = frandombetween(ASTEROIDBREAK_LO_ROTATE,ASTEROIDBREAK_HI_ROTATE); newasteroid->rotinfo.rotspeed.z = frandombetween(ASTEROIDBREAK_LO_ROTATE,ASTEROIDBREAK_HI_ROTATE); } newasteroid->attributes = asteroid->attributes; // inherit attributes bitClear(newasteroid->attributes,ATTRIBUTES_KillerCollDamage|ATTRIBUTES_HeadShotKillerCollDamage); if (asteroidtype != Asteroid0) univAddObjToRenderListIf((SpaceObj *)newasteroid,(SpaceObj *)asteroid); // add to render list if parent is in render list } } } } /*----------------------------------------------------------------------------- Name : AsteroidTakesDamage Description : applies damage to asteroid, causing it to use RU's, and possibly die and split into multiple chunks. Inputs : asteroid, damagetaken, targetWasAlive Outputs : Return : returns TRUE if should delete asteroid ----------------------------------------------------------------------------*/ bool AsteroidTakesDamage(Asteroid *asteroid,sdword damagetaken,bool targetWasAlive) { sdword RUdamage; if (!targetWasAlive) { return FALSE; // target already dead, so ignore } dbgAssert(asteroid->asteroidtype < NUM_ASTEROIDTYPES); RUdamage = (sdword) (damagetaken * ASTEROID_RU_DAMAGE_RATIO[asteroid->asteroidtype]); asteroid->resourcevalue -= RUdamage; if (asteroid->resourcevalue <= 0) { return TRUE; // all RU's have been sapped out. Just kill asteroid } if (asteroid->health <= 0) { return TRUE; } HandleAsteroidScaling(asteroid); return FALSE; } /*----------------------------------------------------------------------------- Name : CollectResources Description : if it is a ship-resource collision, the resource will be collected assuming there is room in the ship for the resource. Inputs : Outputs : Return : TRUE if ship collected resource ----------------------------------------------------------------------------*/ bool CollectResources(Ship *ship,Resource *resource) { ShipStaticInfo *shipstatic; sdword eat; bool resourceWasAlive; dbgAssert(ship->objtype == OBJ_ShipType); shipstatic = (ShipStaticInfo *)ship->staticinfo; if (shipstatic->maxresources == 0) { return FALSE; // not able to collect resources, so return FALSE } if (ship->resources >= shipstatic->maxresources) { return FALSE; // ship full, can't collect resources } if ((universe.univUpdateCounter & shipstatic->harvestRate) == 0) { resourceWasAlive = ((resource->flags & SOF_Dead) == 0); eat = (shipstatic->harvestAmount <= resource->resourcevalue) ? shipstatic->harvestAmount : resource->resourcevalue; ship->resources += eat; if (ship->resources > shipstatic->maxresources) { // over eated eat -= (ship->resources - shipstatic->maxresources); ship->resources = shipstatic->maxresources; speechEvent(ship, STAT_ResCol_Full, 0); } resource->resourcevalue -= eat; if ((resource->resourcevalue <= 0) && (resourceWasAlive)) { AddResourceToDeleteResourceList(resource); bitSet(ship->specialFlags, SPECIAL_Finished_Resource); bitClear(ship->specialFlags, SPECIAL_Resourcing); } else { if (resource->objtype == OBJ_AsteroidType) { HandleAsteroidScaling((Asteroid *)resource); } else if (resource->objtype == OBJ_DustType) { HandleDustCloudScaling((DustCloud *)resource); } else if (resource->objtype == OBJ_GasType) { HandleGasCloudScaling((GasCloud *)resource); } else if (resource->objtype == OBJ_NebulaType) { HandleNebulaScaling((Nebula*)resource); } } } return TRUE; } /*----------------------------------------------------------------------------- Name : TurnHarvestEffectOff Description : turns the harvest effect off Inputs : ship Outputs : Return : ----------------------------------------------------------------------------*/ void TurnHarvestEffectOff(Ship *ship) { dbgAssert(ship->rceffect != NULL); if (bitTest(((etgeffectstatic *)ship->rceffect->staticinfo)->specialOps, ESO_SelfDeleting)) { //if the effect will delete itself ((real32 *)ship->rceffect->variable)[ETG_ResourceDurationParam] = ship->rceffect->timeElapsed; //time it out } else { //else it's a free-running effect... delete it etgEffectDelete(ship->rceffect); univRemoveObjFromRenderList((SpaceObj *)ship->rceffect); listDeleteNode(&ship->rceffect->objlink); } soundEventStop(ship->soundevent.specialHandle); ship->rceffect = NULL; } /*----------------------------------------------------------------------------- Name : Description : Inputs : Outputs : Return : ----------------------------------------------------------------------------*/ void TurnOffAnyResourcingEffects(struct CommandToDo *todo) { Ship *ship = todo->selection->ShipPtr[0]; dbgAssert(todo->selection->numShips == 1); if (ship->rceffect != NULL) { TurnHarvestEffectOff(ship); } } /*----------------------------------------------------------------------------- Name : TurnHarvestEffectOn Description : turns the harvest effect on Inputs : ship, resource, heading, dist etgLOD - which harvesting effect to use resourceRadius - radius of the resource we are harvesting Outputs : Return : ----------------------------------------------------------------------------*/ void TurnHarvestEffectOn(Ship *ship,Resource *resource,vector *trajectory, real32 resourceRadius, etglod *etgLOD) { ShipStaticInfo *shipstatic = (ShipStaticInfo *)ship->staticinfo; ResNozzleStatic *resNozzleStatic = shipstatic->resNozzleStatic; vector nozzleposition; matrix nozzlecoordsys; vector nozzletrajectory; real32 nozzletrajectorydist; etgeffectstatic *stat; sdword LOD; udword intLength; udword intWidth; real32 temp; dbgAssert(resNozzleStatic); matMultiplyMatByVec(&nozzleposition,&ship->rotinfo.coordsys,&resNozzleStatic->position); vecSub(nozzletrajectory,*trajectory,nozzleposition); nozzletrajectorydist = fsqrt(vecMagnitudeSquared(nozzletrajectory)); vecDivideByScalar(nozzletrajectory,nozzletrajectorydist,temp); matCreateCoordSysFromHeading(&nozzlecoordsys,&nozzletrajectory); intLength = TreatAsUdword(nozzletrajectorydist); intWidth = TreatAsUdword(resourceRadius); vecAddTo(nozzleposition,ship->posinfo.position); //create an effect for bullet, if applicable if (etgLOD != NULL) { LOD = ship->currentLOD; if (LOD >= etgLOD->nLevels) { stat = NULL; } else { stat = etgLOD->level[LOD]; } } else { stat = NULL; } #if ETG_DISABLEABLE if (stat != NULL && etgEffectsEnabled && !etgFrequencyExceeded(stat)) #else if (stat != NULL && !etgFrequencyExceeded(stat)) #endif { if (resource->objtype == OBJ_AsteroidType) ship->soundevent.specialHandle = soundEvent(ship, Ship_ResourceAsteroid); else //if (resource->objtype == OBJ_DustType) ship->soundevent.specialHandle = soundEvent(ship, Ship_ResourceDustCloud); /* should have other resource types too? */ ship->rceffect = etgEffectCreate(stat, ship, &nozzleposition, &ship->posinfo.velocity, &nozzlecoordsys, 1.0f, EAF_Velocity | EAF_NLips, 2, intLength, intWidth); } else { ship->rceffect = NULL; //play no effect } } /*----------------------------------------------------------------------------- Name : ModifyHarvestEffect Description : Modify the coordinate system, length and width of a resourcing effect. Inputs : resourceRadius - radius of the resource we are harvesting Outputs : Return : ----------------------------------------------------------------------------*/ void ModifyHarvestEffect(Effect *effect,Ship *ship,vector *trajectory, real32 resourceRadius) { ShipStaticInfo *shipstatic = (ShipStaticInfo *)ship->staticinfo; ResNozzleStatic *resNozzleStatic = shipstatic->resNozzleStatic; vector nozzleposition; vector nozzletrajectory; real32 nozzletrajectorydist; real32 temp; dbgAssert(resNozzleStatic); dbgAssert(effect); matMultiplyMatByVec(&nozzleposition,&ship->rotinfo.coordsys,&resNozzleStatic->position); vecSub(nozzletrajectory,*trajectory,nozzleposition); vecAdd(effect->posinfo.position,nozzleposition,ship->posinfo.position); nozzletrajectorydist = fsqrt(vecMagnitudeSquared(nozzletrajectory)); vecDivideByScalar(nozzletrajectory,nozzletrajectorydist,temp); matCreateCoordSysFromHeading(&effect->rotinfo.coordsys,&nozzletrajectory); ((real32 *)effect->variable)[ETG_ResourceLengthParam] = nozzletrajectorydist; ((real32 *)effect->variable)[ETG_ResourceRadiusParam] = resourceRadius; } void DefaultShipHarvestsResource(struct Ship *ship,struct Resource *resource) { if (!MoveReachedDestinationVariable(ship,&resource->posinfo.position,COLLECTRESOURCE_DIST)) { aishipFlyToPointAvoidingObjsWithVel(ship,&resource->posinfo.position,AISHIP_FastAsPossible + AISHIP_FirstPointInDirectionFlying + AISHIP_PointInDirectionFlying,0.0f,&resource->posinfo.velocity); } else { aitrackSteadyShip(ship); CollectResources(ship,resource); } } #define R1RCASTEROID_APPROACHASTEROID 1 #define R1RCASTEROID_HARVEST_ON 2 #define R1RCASTEROID_HARVEST 3 /*----------------------------------------------------------------------------- Name : R1ResourcerHarvestsAsteroid Description : Used to be only for asteroids, now generalized Inputs : Outputs : Return : ----------------------------------------------------------------------------*/ void R1ResourcerHarvestsAsteroid(struct Ship *ship,struct Resource *resource) { ShipStaticInfo *shipstatic = (ShipStaticInfo *)ship->staticinfo; vector trajectory; real32 range; real32 dist; real32 temp; etglod *lod; real32 resourceRadius; // dbgAssert(resource->objtype == OBJ_AsteroidType); switch (ship->rcstate1) { case 0: // initialize any data ship->rcstate1 = R1RCASTEROID_APPROACHASTEROID; // deliberately fall through to R1RCASTEROID_APPROACHASTEROID case R1RCASTEROID_APPROACHASTEROID: aishipGetTrajectory(ship,(SpaceObjRotImpTarg *)resource,&trajectory); dist = fsqrt(vecMagnitudeSquared(trajectory)); range = RangeToTargetGivenDist(ship,(SpaceObjRotImpTarg *)resource,dist); if (range > ASTEROID_HARVEST_RANGE) { if (aishipFlyToPointAvoidingObjsWithVel(ship,&resource->posinfo.position,AISHIP_FastAsPossible + AISHIP_FirstPointInDirectionFlying + AISHIP_PointInDirectionFlying + AISHIP_DontFlyToObscuredPoints,0.0f,&resource->posinfo.velocity) & AISHIP_FLY_OBJECT_IN_WAY) resource->resourceNotAccessible = (UNIVERSE_UPDATE_RATE * RESOURCE_NOT_ACCESSIBLE_SECONDS); break; } #ifdef DEBUG_COLLECTRESOURCES dbgMessagef("\n%x Changing to harvest",ship); #endif ship->rcstate1 = R1RCASTEROID_HARVEST_ON; // deliberately fall through to R1RCASTEROID_HARVEST_ON case R1RCASTEROID_HARVEST_ON: // turn on harvest effect here if (resource->objtype == OBJ_AsteroidType) { lod = etgResourceEffectTable[ship->shiprace][ETG_AsteroidEffect]; } else { lod = etgResourceEffectTable[ship->shiprace][ETG_GaseousEffect]; } resourceRadius = resource->staticinfo->staticheader.staticCollInfo.collspheresize; TurnHarvestEffectOn(ship,resource,&trajectory, resourceRadius, lod); ship->rcstate1 = R1RCASTEROID_HARVEST; break; case R1RCASTEROID_HARVEST: aishipGetTrajectory(ship,(SpaceObjRotImpTarg *)resource,&trajectory); dist = fsqrt(vecMagnitudeSquared(trajectory)); range = RangeToTargetGivenDist(ship,(SpaceObjRotImpTarg *)resource,dist); if(ship->rceffect == NULL) { //if the effect isn't playing...lets play it... if (resource->objtype == OBJ_AsteroidType) { lod = etgResourceEffectTable[ship->shiprace][ETG_AsteroidEffect]; } else { lod = etgResourceEffectTable[ship->shiprace][ETG_GaseousEffect]; } resourceRadius = resource->staticinfo->staticheader.staticCollInfo.collspheresize; TurnHarvestEffectOn(ship,resource,&trajectory, resourceRadius, lod); } if (range > ASTEROID_MAXHARVEST_RANGE) { // turn off effect here if (ship->rceffect != NULL) { TurnHarvestEffectOff(ship); } dbgAssert(ship->rceffect == NULL); ship->rcstate1 = R1RCASTEROID_APPROACHASTEROID; break; } // recalculate effect coordsys and length of effect if (ship->rceffect != NULL) { resourceRadius = resource->staticinfo->staticheader.staticCollInfo.collspheresize; ModifyHarvestEffect(ship->rceffect,ship,&trajectory, resourceRadius); } // harvest RU's CollectResources(ship,resource); vecDivideByScalar(trajectory,dist,temp); // circle the asteroid if harvesting big asteroids if ((resource->objtype == OBJ_AsteroidType) && (((Asteroid *)resource)->asteroidtype < (AsteroidType)CIRCLE_HARVEST_ASTEROIDS_BIGGEREQUAL_THAN)) { aitrackHeading(ship,&trajectory,FLYSHIP_HEADINGACCURACY); aitrackZeroVelocity(ship); } else { #if 0 real32 forwardvelocity; vector heading; aitrackRightVector(ship,&trajectory,FLYSHIP_HEADINGACCURACY); matGetVectFromMatrixCol3(heading,ship->rotinfo.coordsys); forwardvelocity = vecDotProduct(heading,ship->posinfo.velocity); if (forwardvelocity < CIRCLE_FORWARD_VELOCITY) { physApplyForceToObj((SpaceObj *)ship,ship->nonstatvars.thruststrength[TRANS_FORWARD]*CIRCLE_FORWARD_THRUST,TRANS_FORWARD); } if (range > ASTEROID_HARVEST_RANGE) { aishipFlyToPointAvoidingObjsWithVel(ship,&resource->posinfo.position,0,0.0f,&resource->posinfo.velocity); } #else real32 rightvelocity; vector right; aitrackHeading(ship,&trajectory,FLYSHIP_HEADINGACCURACY); matGetVectFromMatrixCol2(right,ship->rotinfo.coordsys); rightvelocity = vecDotProduct(right,ship->posinfo.velocity); if (rightvelocity < CIRCLE_RIGHT_VELOCITY) { // dbgMessagef("\nApplying right thrust %f",rightvelocity); physApplyForceToObj((SpaceObj *)ship,ship->nonstatvars.thruststrength[TRANS_RIGHT]*CIRCLE_RIGHT_THRUST,TRANS_RIGHT); } if (range > ASTEROID_HARVEST_RANGE) { // dbgMessage("\nGoing back towards"); aishipFlyToPointAvoidingObjsWithVel(ship,&resource->posinfo.position,0,0.0f,&resource->posinfo.velocity); } else { aitrackZeroForwardVelocity(ship); } #endif } break; } return; } /*----------------------------------------------------------------------------- Name : InitShipForResourceCollection Description : initializes ship ai variables for resource collection Inputs : ship Outputs : Return : ----------------------------------------------------------------------------*/ void InitShipForResourceCollection(Ship *ship,Resource *resource) { InitShipAI(ship,TRUE); ship->rcstate1 = 0; ship->rcstate2 = 0; ship->ShipXHarvestsResourceY = R1ResourcerHarvestsAsteroid;//DefaultShipHarvestsResource; } #pragma warning( 4 : 4047) // turns off "different levels of indirection warning" void PreFix_ShipXHarvestsResourceY(Ship *ship) { if (ship->ShipXHarvestsResourceY == DefaultShipHarvestsResource) { ship->ShipXHarvestsResourceY = 0; } else if (ship->ShipXHarvestsResourceY == R1ResourcerHarvestsAsteroid) { ship->ShipXHarvestsResourceY = 1; } else { ship->ShipXHarvestsResourceY = -1; } } void Fix_ShipXHarvestsResourceY(Ship *ship) { switch ((sdword)ship->ShipXHarvestsResourceY) { case 0: ship->ShipXHarvestsResourceY = DefaultShipHarvestsResource; break; case 1: ship->ShipXHarvestsResourceY = R1ResourcerHarvestsAsteroid; break; default: ship->ShipXHarvestsResourceY = NULL; break; } } #pragma warning( 2 : 4047) // turn back on "different levels of indirection warning" /*----------------------------------------------------------------------------- Name : processCollectResource Description : processes a collect resource command Inputs : Outputs : Return : ----------------------------------------------------------------------------*/ bool processCollectResource(struct CommandToDo *collecttodo) { sdword numShips = collecttodo->selection->numShips; Ship *ship; ShipStaticInfo *shipstatic; Resource *resource = collecttodo->collect.resource; Ship *dockship; dbgAssert(numShips > 0); dbgAssert(numShips == 1); ship = collecttodo->selection->ShipPtr[0]; shipstatic = (ShipStaticInfo *)ship->staticinfo; if (ship->resources >= shipstatic->maxresources) { TurnOffAnyResourcingEffects(collecttodo); collecttodo->collect.resource = NULL; // override - don't collect resource if full resource = NULL; goto letsdock; } if (resource == NULL) { if ((ship->resources < shipstatic->resourcesAtOneTime) && ((resource = rescollectFindNearestResource(ship,collecttodo)) != NULL)) { #ifdef DEBUG_COLLECTRESOURCES dbgMessage("\nCollecting next resource..."); #endif InitShipForResourceCollection(ship,resource); collecttodo->collect.resource = resource; return FALSE; } else { letsdock: if ((ship->resources > 0) && ((dockship = FindNearestShipToDockAt(ship,DOCK_TO_DEPOSIT_RESOURCES)) != NULL)) { #ifdef DEBUG_COLLECTRESOURCES dbgMessage("\nFlying back to deposit resources..."); #endif // changing COMMAND_COLLECTRESOURCE to COMMAND_DOCK FreeLastOrder(collecttodo); // free COMMAND_COLLECTRESOURCE dockChangeSingleShipToDock(collecttodo,ship,dockship,TRUE,DOCK_TO_DEPOSIT_RESOURCES); return FALSE; } else { if (ship->playerowner->playerIndex) { tutGameMessage("Game_NonPlayerHarvesterDone"); } else { tutGameMessage("Game_PlayerHarvesterDone"); } speechEventFleet(STAT_F_ResourcesAllHarvested, 0, ship->playerowner->playerIndex); return TRUE; } } } else { if (ResourceMovingTooFast(resource)) { // resource moving too fast, so pick a different one. TurnOffAnyResourcingEffects(collecttodo); collecttodo->collect.resource = NULL; return FALSE; } if (ResourceAlreadyBeingHarvested(&universe.mainCommandLayer,collecttodo,resource)) { // resource already being harvested by someone else, so pick a different one. TurnOffAnyResourcingEffects(collecttodo); collecttodo->collect.resource = NULL; return FALSE; } if ((universe.univUpdateCounter & CHECK_HARV_OUTOFWORLD_RESOURCE_RATE) == (resource->resourceID.resourceNumber & CHECK_HARV_OUTOFWORLD_RESOURCE_RATE)) { if (univObjectOutsideWorld((SpaceObj *)resource)) { TurnOffAnyResourcingEffects(collecttodo); collecttodo->collect.resource = NULL; return FALSE; } } ship->ShipXHarvestsResourceY(ship,resource); if (resource->resourceNotAccessible) { TurnOffAnyResourcingEffects(collecttodo); collecttodo->collect.resource = NULL; return FALSE; } return FALSE; } } /*----------------------------------------------------------------------------- Name : R1ResourcerAttacksShip Description : used for resourcers attacking ships Inputs : Outputs : Return : ----------------------------------------------------------------------------*/ void R1ResourcerAttacksShip(struct Ship *ship,struct SpaceObjRotImpTarg *target,bool passiveAttacking) { ShipStaticInfo *shipstatic = (ShipStaticInfo *)ship->staticinfo; vector trajectory; real32 range; real32 dist; real32 temp; etglod *lod; real32 targetRadius; // dbgAssert(resource->objtype == OBJ_AsteroidType); switch (ship->rcstate1) { case 0: // initialize any data ship->rcstate1 = R1RCASTEROID_APPROACHASTEROID; // deliberately fall through to R1RCASTEROID_APPROACHASTEROID case R1RCASTEROID_APPROACHASTEROID: aishipGetTrajectory(ship,(SpaceObjRotImpTarg *)target,&trajectory); dist = fsqrt(vecMagnitudeSquared(trajectory)); range = RangeToTargetGivenDist(ship,(SpaceObjRotImpTarg *)target,dist); if (range > ASTEROID_HARVEST_RANGE) { aishipFlyToPointAvoidingObjsWithVel(ship,&target->posinfo.position,AISHIP_FastAsPossible + AISHIP_FirstPointInDirectionFlying + AISHIP_PointInDirectionFlying,0.0f,&target->posinfo.velocity); break; } dbgMessagef("\nShip Changing to Harvest/attack mode."); ship->rcstate1 = R1RCASTEROID_HARVEST_ON; // deliberately fall through to R1RCASTEROID_HARVEST_ON case R1RCASTEROID_HARVEST_ON: lod = etgResourceEffectTable[ship->shiprace][ETG_AsteroidEffect]; targetRadius = target->staticinfo->staticheader.staticCollInfo.collspheresize; TurnHarvestEffectOn(ship,(Resource *)target,&trajectory, targetRadius, lod); ship->rcstate1 = R1RCASTEROID_HARVEST; break; case R1RCASTEROID_HARVEST: aishipGetTrajectory(ship,(SpaceObjRotImpTarg *)target,&trajectory); dist = fsqrt(vecMagnitudeSquared(trajectory)); range = RangeToTargetGivenDist(ship,(SpaceObjRotImpTarg *)target,dist); if (range > ASTEROID_MAXHARVEST_RANGE) { // turn off effect here if (ship->rceffect != NULL) { TurnHarvestEffectOff(ship); } dbgAssert(ship->rceffect == NULL); ship->rcstate1 = R1RCASTEROID_APPROACHASTEROID; break; } if(ship->rceffect == NULL) { lod = etgResourceEffectTable[ship->shiprace][ETG_AsteroidEffect]; targetRadius = target->staticinfo->staticheader.staticCollInfo.collspheresize; TurnHarvestEffectOn(ship,(Resource *)target,&trajectory, targetRadius, lod); } // recalculate effect coordsys and length if (ship->rceffect != NULL) { targetRadius = target->staticinfo->staticheader.staticCollInfo.collspheresize; ModifyHarvestEffect(ship->rceffect,ship,&trajectory, targetRadius); } // harvest RU's //CollectResources(ship,resource); ApplyDamageToTarget(target,TW_HARVESTER_DAMAGE_PER_SECOND*universe.phystimeelapsed,-1,DEATH_Killed_By_Player,ship->playerowner->playerIndex); vecDivideByScalar(trajectory,dist,temp); // circle the asteroid if harvesting big asteroids //if ((resource->objtype == OBJ_AsteroidType) && // (((Asteroid *)resource)->asteroidtype < (AsteroidType)CIRCLE_HARVEST_ASTEROIDS_BIGGEREQUAL_THAN)) //{ // aitrackHeading(ship,&trajectory,FLYSHIP_HEADINGACCURACY); // aitrackZeroVelocity(ship); //} // //else { #if 0 real32 forwardvelocity; vector heading; aitrackRightVector(ship,&trajectory,FLYSHIP_HEADINGACCURACY); matGetVectFromMatrixCol3(heading,ship->rotinfo.coordsys); forwardvelocity = vecDotProduct(heading,ship->posinfo.velocity); if (forwardvelocity < CIRCLE_FORWARD_VELOCITY) { physApplyForceToObj((SpaceObj *)ship,ship->nonstatvars.thruststrength[TRANS_FORWARD]*CIRCLE_FORWARD_THRUST,TRANS_FORWARD); } if (range > ASTEROID_HARVEST_RANGE) { aishipFlyToPointAvoidingObjsWithVel(ship,&target->posinfo.position,0,0.0f,&target->posinfo.velocity); } #else real32 rightvelocity; vector right; aitrackHeading(ship,&trajectory,FLYSHIP_HEADINGACCURACY); matGetVectFromMatrixCol2(right,ship->rotinfo.coordsys); rightvelocity = vecDotProduct(right,ship->posinfo.velocity); if (rightvelocity < CIRCLE_RIGHT_VELOCITY) { // dbgMessagef("\nApplying right thrust %f",rightvelocity); physApplyForceToObj((SpaceObj *)ship,ship->nonstatvars.thruststrength[TRANS_RIGHT]*CIRCLE_RIGHT_THRUST,TRANS_RIGHT); } if (range > ASTEROID_HARVEST_RANGE) { // dbgMessage("\nGoing back towards"); aishipFlyToPointAvoidingObjsWithVel(ship,&target->posinfo.position,0,0.0f,&target->posinfo.velocity); } else { aitrackZeroForwardVelocity(ship); } #endif } break; } return; } void removeResourcerFromAttacking(Ship *ship) { if (ship->rceffect != NULL) { TurnHarvestEffectOff(ship); } }