From 9a96d0bff56f1969c68bb52a2f33296095bdc67d Mon Sep 17 00:00:00 2001 From: BobTheBob <32057864+BobTheBob9@users.noreply.github.com> Date: Tue, 31 Aug 2021 23:14:58 +0100 Subject: move to new mod format --- .../mod/scripts/vscripts/ai/_ai_marvin_jobs.gnut | 600 +++++++++++++++++++++ 1 file changed, 600 insertions(+) create mode 100644 Northstar.CustomServers/mod/scripts/vscripts/ai/_ai_marvin_jobs.gnut (limited to 'Northstar.CustomServers/mod/scripts/vscripts/ai/_ai_marvin_jobs.gnut') diff --git a/Northstar.CustomServers/mod/scripts/vscripts/ai/_ai_marvin_jobs.gnut b/Northstar.CustomServers/mod/scripts/vscripts/ai/_ai_marvin_jobs.gnut new file mode 100644 index 000000000..588b4d75e --- /dev/null +++ b/Northstar.CustomServers/mod/scripts/vscripts/ai/_ai_marvin_jobs.gnut @@ -0,0 +1,600 @@ + +/* + ToDo: + -if marvin has no jobs to go to make him to back to spawn position instead of standing at last node +*/ + +global function MarvinJobs_Init +global function MarvinJobThink +global function GetMarvinType + +const DEBUG_MARVIN_JOBS = false +const MAX_JOB_SEARCH_DIST_SQR = 1000 * 1000 +const JOB_NODE_COOLDOWN_TIME = 15.0 + +struct MarvinJob +{ + string validMarvinType + entity node + entity user + string jobType + bool tempJob + float nextUsableTime = 0 + entity barrel +} + +struct +{ + array marvinJobs + table jobFunctions +} file + + + + +// ██╗███╗ ██╗██╗████████╗ +// ██║████╗ ██║██║╚══██╔══╝ +// ██║██╔██╗ ██║██║ ██║ +// ██║██║╚██╗██║██║ ██║ +// ██║██║ ╚████║██║ ██║ +// ╚═╝╚═╝ ╚═══╝╚═╝ ╚═╝ + +void function MarvinJobs_Init() +{ + file.jobFunctions[ "welding" ] <- SimpleJobAnims + file.jobFunctions[ "welding_under" ] <- SimpleJobAnims + file.jobFunctions[ "window" ] <- SimpleJobAnims + file.jobFunctions[ "fightFire" ] <- SimpleJobAnims + file.jobFunctions[ "barrel_pickup" ] <- MarvinPicksUpBarrel + file.jobFunctions[ "barrel_putdown" ] <- MarvinPutsDownBarrel + file.jobFunctions[ "repair_over_edge" ] <- SimpleJobAnims + file.jobFunctions[ "repair_above" ] <- SimpleJobAnims + file.jobFunctions[ "repair_under" ] <- SimpleJobAnims + file.jobFunctions[ "datacards" ] <- SimpleJobAnims + + file.jobFunctions[ "drone_welding" ] <- SimpleJobAnims + file.jobFunctions[ "drone_inspect" ] <- SimpleJobAnims + + RegisterSignal( "pickup_barrel" ) + RegisterSignal( "putdown_barrel" ) + RegisterSignal( "JobStarted" ) + RegisterSignal( "StopDoingJobs" ) + + AddSpawnCallback( "script_marvin_job", InitMarvinJob ) + + AddCallback_EntitiesDidLoad( MarvinJobsEntitiesDidLoad ) +} + +void function InitMarvinJob( entity node ) +{ + Assert( node.HasKey( "job" ) ) + Assert( node.kv.job != "" ) + Assert( string( node.kv.job ) in file.jobFunctions, "Marvin job node at " + node.GetOrigin() + " has unhandled job type " + string( node.kv.job ) ) + string editorClass = GetEditorClass( node ) + + // Drop node to ground for certain types or if checked on the entity + if ( editorClass == "" ) + { + if ( !node.HasKey( "hover" ) || node.kv.hover != "1" ) + DropToGround( node ) + } + + if ( DEBUG_MARVIN_JOBS ) + DebugDrawAngles( node.GetOrigin(), node.GetAngles() ) + + // Create marvin job struct + MarvinJob marvinJob + marvinJob.node = node + marvinJob.jobType = string( node.kv.job ) + marvinJob.tempJob = node.HasKey( "tempJob" ) && node.kv.tempJob == "1" + + if ( marvinJob.jobType == "barrel_pickup" ) + marvinJob.barrel = CreateBarrel( node ) + + // Set what marvin_type of NPC can use this job + switch ( editorClass ) + { + case "script_marvin_drone_job": + marvinJob.validMarvinType = "marvin_type_drone" + break + default: + marvinJob.validMarvinType = "marvin_type_walker" + break + } + + file.marvinJobs.append( marvinJob ) +} + +void function MarvinJobsEntitiesDidLoad() +{ + if ( DEBUG_MARVIN_JOBS ) + DebugMarvinJobs() +} + + + + + +// ████████╗██╗ ██╗██╗███╗ ██╗██╗ ██╗ +// ╚══██╔══╝██║ ██║██║████╗ ██║██║ ██╔╝ +// ██║ ███████║██║██╔██╗ ██║█████╔╝ +// ██║ ██╔══██║██║██║╚██╗██║██╔═██╗ +// ██║ ██║ ██║██║██║ ╚████║██║ ██╗ +// ╚═╝ ╚═╝ ╚═╝╚═╝╚═╝ ╚═══╝╚═╝ ╚═╝ + +void function MarvinJobThink( entity marvin ) +{ + EndSignal( marvin, "OnDeath" ) + EndSignal( marvin, "OnDestroy" ) + EndSignal( marvin, "StopDoingJobs" ) + + // Wait a frame because npcs that are spawned at map load may run this function before job nodes are finished being initialized + WaitFrame() + + // Get all jobs this marvin can do + array jobs = GetJobsForMarvin( marvin ) + if ( jobs.len() == 0 ) + return + + OnThreadEnd( + function() : ( marvin ) + { + Assert( !IsAlive( marvin ), "MarvinJobThink ended but the marvin is still alive" ) + } + ) + + while ( true ) + { + foreach ( MarvinJob job in jobs ) + { + waitthread MarvinDoJob( marvin, job ) + WaitFrame() + } + + jobs.randomize() + WaitFrame() + } +} + +void function MarvinDoJob( entity marvin, MarvinJob job ) +{ + Assert( IsAlive( marvin ), "Marvin " + marvin + " is not alive" ) + EndSignal( marvin, "OnFailedToPath" ) + EndSignal( marvin, "OnDeath" ) + + // Don't do a job that's already in use or not ready to be used again + if ( IsValid( job.user ) || Time() < job.nextUsableTime ) + return + + // Don't use a barrel put down job if you can'r carrying a barrel + if ( job.jobType == "barrel_putdown" && !IsValid( marvin.ai.carryBarrel ) ) + return + + // If you're carrying a barrel, only do a barrel put down job + if ( IsValid( marvin.ai.carryBarrel ) && job.jobType != "barrel_putdown" ) + return + + OnThreadEnd( + function() : ( job ) + { + job.user = null + job.nextUsableTime = Time() + JOB_NODE_COOLDOWN_TIME + } + ) + + // Default walk anim + MarvinDefaultMoveAnim( marvin ) + + // Node gets occupied + job.user = marvin + + if ( DEBUG_MARVIN_JOBS ) + DebugDrawLine( marvin.GetWorldSpaceCenter(), job.node.GetOrigin(), 255, 0, 0, true, 3.0 ) + + // Run the job function + thread DontDisableJobOnPathFailOrDeath( marvin, job ) + waitthread file.jobFunctions[ job.jobType ]( marvin, job ) + if ( IsValid( marvin ) ) + marvin.Anim_Stop() +} + +void function DontDisableJobOnPathFailOrDeath( entity marvin, MarvinJob job ) +{ + EndSignal( marvin, "JobStarted" ) + WaitSignal( marvin, "OnFailedToPath", "OnDeath" ) + job.nextUsableTime = Time() +} + + + + + +// ██╗ ██████╗ ██████╗ ███████╗██╗ ██╗███╗ ██╗ ██████╗████████╗██╗ ██████╗ ███╗ ██╗███████╗ +// ██║██╔═══██╗██╔══██╗ ██╔════╝██║ ██║████╗ ██║██╔════╝╚══██╔══╝██║██╔═══██╗████╗ ██║██╔════╝ +// ██║██║ ██║██████╔╝ █████╗ ██║ ██║██╔██╗ ██║██║ ██║ ██║██║ ██║██╔██╗ ██║███████╗ +// ██ ██║██║ ██║██╔══██╗ ██╔══╝ ██║ ██║██║╚██╗██║██║ ██║ ██║██║ ██║██║╚██╗██║╚════██║ +// ╚█████╔╝╚██████╔╝██████╔╝ ██║ ╚██████╔╝██║ ╚████║╚██████╗ ██║ ██║╚██████╔╝██║ ╚████║███████║ +// ╚════╝ ╚═════╝ ╚═════╝ ╚═╝ ╚═════╝ ╚═╝ ╚═══╝ ╚═════╝ ╚═╝ ╚═╝ ╚═════╝ ╚═╝ ╚═══╝╚══════╝ + +void function SimpleJobAnims( entity marvin, MarvinJob job ) +{ + // Get the anims to use for the job + array anims + switch ( job.jobType ) + { + // Marvin jobs + case "welding": + anims.append( "mv_idle_weld" ) + break + case "welding_under": + anims.append( "mv_weld_under" ) + anims.append( "mv_weld_under" ) + anims.append( "mv_weld_under_stumble" ) + break + case "window": + anims.append( "mv_idle_wash_window_noloop" ) + anims.append( "mv_idle_buff_window_noloop" ) + break + case "fightFire": + anims.append( "mv_fireman_idle" ) + anims.append( "mv_fireman_shift" ) + break + case "repair_over_edge": + anims.append( "mv_repair_overedge" ) + anims.append( "mv_repair_overedge" ) + anims.append( "mv_repair_overedge_stumble" ) + break + case "repair_above": + anims.append( "mv_repair_ship_above" ) + break + case "repair_under": + anims.append( "mv_repair_under" ) + anims.append( "mv_repair_under_stumble" ) + break + case "datacards": + anims.append( "mv_job_replace_datacards" ) + break + + // Marvin drone jobs + case "drone_welding": + anims.append( "dw_jobs_welding_wallpanel" ) + break + case "drone_inspect": + anims.append( "inspect1" ) + anims.append( "inspect2" ) + break + } + Assert( anims.len() > 0 ) + + if ( IsMarvinWalker( marvin ) ) + waitthread MarvinRunToAnimStart( marvin, anims[0], job.node ) + else + waitthread MarvinFlyToAnimStart( marvin, anims[0], job.node ) + + Signal( marvin, "JobStarted" ) + + while ( true ) + { + anims.randomize() + foreach ( string anim in anims ) + { + float animLength = marvin.GetSequenceDuration( anim ) // wait anim length because some anims may be looping so we can't wait for them to end + + if ( IsMarvinDrone( marvin ) ) + thread PlayAnimTeleport( marvin, anim, job.node ) + else + thread PlayAnim( marvin, anim, job.node, null, 0.6 ) + + wait animLength + } + if ( job.tempJob ) + break + } +} + +void function MarvinPicksUpBarrel( entity marvin, MarvinJob job ) +{ + // Don't try to pick up a barrel if there isn't one nearby + if ( !IsValid( job.barrel ) ) + return + if ( Distance( job.node.GetOrigin(), job.barrel.GetOrigin() ) > 25 ) + return + + EndSignal( job.barrel, "OnDestroy" ) + + entity info_target = CreateEntity( "info_target" ) + DispatchSpawn( info_target ) + + OnThreadEnd( + function () : ( info_target ) + { + info_target.Destroy() + } + ) + + vector barrelFlatAngles = job.barrel.GetAngles() + barrelFlatAngles.x = 0 + barrelFlatAngles.z = 0 + + info_target.SetOrigin( job.barrel.GetOrigin() ) + info_target.SetAngles( barrelFlatAngles ) + + DropToGround( info_target ) + + if ( info_target.GetOrigin().z < -MAX_WORLD_COORD ) + return // Fell through map + + if ( DEBUG_MARVIN_JOBS ) + thread DrawAnglesForMovingEnt( info_target, 30.0 ) + + + // Go to the barrel + MarvinRunToAnimStart( marvin, "mv_carry_barrel_pickup", info_target ) + + // Try to pick it up + thread PlayAnim( marvin, "mv_carry_barrel_pickup", info_target, null, 0.6 ) + + // Wait until animation should pick up the barrel + marvin.WaitSignal( "pickup_barrel" ) + + // Get attachment info + string attachment = "PROPGUN" + int attachIndex = marvin.LookupAttachment( attachment ) + vector attachOrigin = marvin.GetAttachmentOrigin( attachIndex ) + + // Make sure the barrel is close when it's time to parent the barrel + if ( Distance( attachOrigin, job.barrel.GetOrigin() ) > 25 ) + { + marvin.Anim_Stop() + return + } + + // Marvin picks up the barrel and carries it + thread MarvinCarryBarrel( marvin, job.barrel ) + + marvin.WaitSignal( "OnAnimationDone" ) +} + +void function MarvinCarryBarrel( entity marvin, entity barrel ) +{ + marvin.EndSignal( "OnDeath" ) + marvin.EndSignal( "OnDamaged" ) + marvin.EndSignal( "putdown_barrel" ) + + OnThreadEnd( + function () : ( marvin, barrel ) + { + if ( IsValid( barrel ) ) + { + barrel.kv.solid = SOLID_VPHYSICS + barrel.ClearParent() + barrel.SetOwner( null ) + EntFireByHandle( barrel, "wake", "", 0, null, null ) + EntFireByHandle( barrel, "enablemotion", "", 0, null, null ) + } + + if ( IsAlive( marvin ) ) + { + MarvinDefaultMoveAnim( marvin ) + marvin.ClearIdleAnim() + marvin.ai.carryBarrel = null + } + } + ) + + string attachment = "PROPGUN" + marvin.SetMoveAnim( "mv_carry_barrel_walk" ) + marvin.SetIdleAnim( "mv_carry_barrel_idle" ) + barrel.SetParent( marvin, attachment, false, 0.5 ) + barrel.SetOwner( marvin ) + + barrel.kv.solid = 0 // not solid + + marvin.ai.carryBarrel = barrel + + WaitSignal( marvin, "OnDestroy" ) +} + +void function MarvinPutsDownBarrel( entity marvin, MarvinJob job ) +{ + Assert( IsValid( marvin.ai.carryBarrel ) ) + + // Don't place a barrel here if there is already one + if ( IsValid( job.barrel ) ) + { + if ( Distance( job.node.GetOrigin(), job.barrel.GetOrigin() ) <= 25 ) + return + } + + EndSignal( marvin.ai.carryBarrel, "OnDestroy" ) + + marvin.SetMoveAnim( "mv_carry_barrel_walk" ) + marvin.SetIdleAnim( "mv_carry_barrel_idle" ) + + // Walk to the put down spot + MarvinRunToAnimStart( marvin, "mv_carry_barrel_putdown", job.node ) + + // Put down the barrel + thread PlayAnim( marvin, "mv_carry_barrel_putdown", job.node, null, 0.6 ) + + // Wait for release + marvin.WaitSignal( "putdown_barrel" ) + + marvin.WaitSignal( "OnAnimationDone" ) +} + + + + +// ██╗ ██╗████████╗██╗██╗ ██╗████████╗██╗ ██╗ +// ██║ ██║╚══██╔══╝██║██║ ██║╚══██╔══╝╚██╗ ██╔╝ +// ██║ ██║ ██║ ██║██║ ██║ ██║ ╚████╔╝ +// ██║ ██║ ██║ ██║██║ ██║ ██║ ╚██╔╝ +// ╚██████╔╝ ██║ ██║███████╗██║ ██║ ██║ +// ╚═════╝ ╚═╝ ╚═╝╚══════╝╚═╝ ╚═╝ ╚═╝ + +bool function IsMarvinWalker( entity marvin ) +{ + return GetMarvinType( marvin ) == "marvin_type_walker" +} + +bool function IsMarvinDrone( entity marvin ) +{ + return GetMarvinType( marvin ) == "marvin_type_drone" +} + +string function GetMarvinType( entity npc ) +{ + var marvinType = npc.Dev_GetAISettingByKeyField( "marvin_type" ) + if ( marvinType == null ) + return "not_marvin" + + return expect string( marvinType ) +} + +bool function IsJobNode( entity node ) +{ + if ( node.GetClassName() == "script_marvin_job" ) + return true + if ( GetEditorClass( node ) == "script_marvin_drone_job" ) + return true + return false +} + +void function MarvinDefaultMoveAnim( entity marvin ) +{ + if ( IsMarvinWalker( marvin ) ) + { + marvin.SetNPCMoveSpeedScale( 1.0 ) + marvin.SetMoveAnim( "walk_all" ) + } +} + +array function GetJobsForMarvin( entity marvin ) +{ + string marvinType = GetMarvinType( marvin ) + + // Get jobs this marvin links to, if any, and randomize + array linkedJobs + array linkedEnts = marvin.GetLinkEntArray() + foreach ( entity ent in linkedEnts ) + { + if ( IsJobNode( ent ) ) + { + MarvinJob linkedJob = GetMarvinJobForNode( ent ) + Assert( IsValid( linkedJob.node ) ) + + // Error if we are linking to the wrong type of job node + Assert( marvinType == linkedJob.validMarvinType, "npc_marvin at " + marvin.GetOrigin() + " links to a marvin job of the wrong marvin_type" ) + + linkedJobs.append( linkedJob ) + } + } + linkedJobs.randomize() + + // If marvin was linked to jobs we only consider those + if ( marvin.HasKey( "LinkedJobsOnly" ) && marvin.kv.LinkedJobsOnly == "1" ) + { + Assert( linkedJobs.len() > 0, "marvin at " + marvin.GetOrigin() + " has LinkedJobsOnly marked but does not link to any job nodes" ) + return linkedJobs + } + + // Add all jobs within valid distance and randomize + array jobs + foreach ( MarvinJob marvinJob in file.marvinJobs ) + { + if ( marvinType != marvinJob.validMarvinType ) + continue + + // Don't re-add a job that was linked to + if ( linkedJobs.contains( marvinJob ) ) + continue + + // Teleport nodes are for special case jobs with no nav mesh do son't consider them automatically + if ( marvinJob.node.HasKey( "teleport" ) && marvinJob.node.kv.teleport == "1" ) + continue + + // Only search for jobs within a max distance + if ( DistanceSqr( marvinJob.node.GetOrigin(), marvin.GetOrigin() ) <= MAX_JOB_SEARCH_DIST_SQR ) + jobs.append( marvinJob ) + } + + // Randomize the order so the marvin does them out of order + jobs.randomize() + + // Add the linked jobs to the list, and put them at the beginning of the priority + foreach ( MarvinJob linkedJob in linkedJobs ) + jobs.insert( 0, linkedJob ) + + // Debug draw jobs this marvin can take + if ( DEBUG_MARVIN_JOBS ) + { + foreach ( MarvinJob job in jobs ) + { + if ( linkedJobs.contains( job ) ) + DebugDrawLine( marvin.GetOrigin(), job.node.GetOrigin(), 255, 255, 0, true, 10.0 ) + else + DebugDrawLine( marvin.GetOrigin(), job.node.GetOrigin(), 200, 200, 200, true, 10.0 ) + } + } + + return jobs +} + +void function DebugMarvinJobs() +{ + while ( true ) + { + foreach ( MarvinJob marvinJob in file.marvinJobs ) + { + string appendText = "AVAILABLE" + float timeTillNextUse = marvinJob.nextUsableTime - Time() + if ( IsValid( marvinJob.user ) ) + appendText = "RESERVED" + else if ( timeTillNextUse > 0 ) + appendText = format( "%.1f", timeTillNextUse ) + DebugDrawText( marvinJob.node.GetOrigin(), marvinJob.jobType + " (" + appendText + ")", true, 0.1 ) + } + wait 0.05 + } +} + +MarvinJob function GetMarvinJobForNode( entity node ) +{ + MarvinJob marvinJob + foreach ( MarvinJob marvinJob in file.marvinJobs ) + { + if ( marvinJob.node == node ) + return marvinJob + } + return marvinJob +} + +entity function CreateBarrel( entity node ) +{ + return CreatePropPhysics( node.GetModelName(), node.GetOrigin(), node.GetAngles() ) +} + +void function MarvinRunToAnimStart( entity marvin, string anim, entity jobNode ) +{ + if ( jobNode.HasKey( "teleport" ) && jobNode.kv.teleport == "1" ) + wait 0.1 + else + RunToAnimStartPos( marvin, anim, jobNode ) +} + +void function MarvinFlyToAnimStart( entity marvin, string anim, entity jobNode ) +{ + if ( jobNode.HasKey( "teleport" ) && jobNode.kv.teleport == "1" ) + { + wait 0.1 + return + } + + AnimRefPoint animStartInfo = marvin.Anim_GetStartForRefPoint( anim, jobNode.GetOrigin(), jobNode.GetAngles() ) + + marvin.AssaultPoint( animStartInfo.origin ) + marvin.AssaultSetAngles( animStartInfo.angles, true ) + marvin.AssaultSetArrivalTolerance( 16 ) + marvin.WaitSignal( "OnFinishedAssault" ) +} \ No newline at end of file -- cgit v1.2.3