Jump to content

havenphillip

Members
  • Content Count

    218
  • Joined

  • Last visited

Community Reputation

37 Excellent

About havenphillip

Profile Information

  • Gender
    Male

Recent Profile Visitors

756 profile views
  1. Thanks for the help, man. I'll probably not do the onAttacked thing. I can't get it to work and that was just like an extra thing anyway. I'm definitely going to study this though and see if I can make it do more stuff. I just need to keep improving my understanding of the thinking behind it. Peace.
  2. Ok so if I do it like this I can get the others to attack, so it appears that the player is somewhere connected to this script. I just removed the "if" statement and told it to set mode to attack: function Script:onAttacked(data) System:Print("Yeah it's calling this function") self.target = data.player self:SetMode("attack") end Here on the raise event we're calling the self.entity:GetPosition as the crawler who is being shot, right? RaiseEvent("onAttacked", { hurtMonsterPosition = self.entity:GetPosition(), player = distributorOfPain}) ...and later in the "if" statement "data.hurtMonsterPosition" is itself from above. So we're telling it to get the distance from itself to itself? if data.hurtMonsterPosition:GetDistance(self.entity:GetPosition(),false) < 1000 then
  3. Ok. Yeah, that works. It does the System:Print(). But it still doesn't appear to be alerting the other crawlers next to it. It seems to have also solved that issue with getting an error when the crawler is beyond the pick range for health. I appreciate your help with this, man. I wish I could be more useful but this is all new to me. It's probably something on my end at this point but I don't even know where to look. I noticed in the onDead event I had to write "self.kills = self.kills +1" in the enemyDied(data) function, but also you put " subId = subId + 1" in the SubscribeEvent(eventName, script, func). Does it need something like that again? My best guess is its in the function itself? This is the function currently. Maybe it's one of these true/false things? or I changed the distance to 500 just for testing but don't know if that would do anything. function Script:onAttacked(data) System:Print("Yeah it's calling this function") -- I don't recall if this is the correct syntax for distance checks but it gets the idea across. you'll have to research distance checks. if data.hurtMonsterPosition:GetDistance(self.entity:GetPosition(true),false) < 500 then -- if this monster is in a given range of the monster that was attacked then assign this monster the player target as well and it'll come running towards it! self.target = data.player end end
  4. I figured out that if I raise the event under within the health > 0 statement I don't get that error, but it still doesn't reach the onAttacked function, as the System:Print() doesn't do anything. if self.health>0 then if self.target==nil then RaiseEvent("onAttacked", { hurtMonsterPosition = self.entity:GetPosition(), player = distributorOfPain}) self.target=distributorOfPain self:SetMode("attack") end Having it like this seemed to let things run smoothly and then for no reason eventually I would get that error. But that appears to be only when I shoot an enemy from so far away that the pick isn't grabbing their name (and health). Since I put the enemy name in the onDead loop maybe that's giving me that error?
  5. Sure. Here you go: import "Scripts/Functions/GetEntityNeighbors.lua" --Public values Script.name = "" Script.despawnTime = 60 --int "Despawn Time" Script.despawnTimer = 0 Script.removeBodyTime = 8 Script.removeBodyTimer = 0 Script.maxhealth=40 --int "Max Health" Script.health=40--int "Health" Script.enabled=true--bool "Enabled" Script.target=nil--entity "Target" Script.sightradius=30--float "Sight Range" Script.senseradius=2--float "Hearing Range" Script.teamid=2--choice "Team" "Neutral,Good,Bad" Script.attackdelay=300--int "Attack delay" Script.animspeedrun=0.03--float "Run anim speed" Script.animspeedDeath= 0.028 --float "Death anim Speed" Script.speed= 5 --float "Move Speed" --Private values Script.damage=20 + math.random(-10,10) Script.attackrange=1.7 Script.updatefrequency=500 Script.lastupdatetime=0 Script.prevtarget=nil Script.followingtarget=false Script.maxaccel=15 Script.lastupdatetargettime=0 Script.attackmode=1 Script.attackbegan=0 Script.attack1sound=""--path "Attack 1 sound" "Wav file (*.wav):wav|Sound" Script.attack2sound=""--path "Attack 2 sound" "Wav file (*.wav):wav|Sound" Script.alertsound=""--path "Alert sound" "Wav file (*.wav):wav|Sound" Script.deathsound=""--path "Death sound" "Wav file (*.wav):wav|Sound" Script.idlesound=""--path "Idle sound" "Wav file (*.wav):wav|Sound" Script.headshotDmg = 7 --headshot multiplier function Script:Enable()--in if self.enabled==false then if self.health>0 then self.enabled=true if self.target~=nil then self:SetMode("roam") else self:SetMode("idle") end end end end function Script:ChooseTarget() local entities = GetEntityNeighbors(self.entity,self.sightradius,true) local k,entity for k,entity in pairs(entities) do if entity.script.teamid~=nil and entity.script.teamid~=0 and entity.script.teamid~=self.teamid then if entity.script.health>0 then local d = self.entity:GetDistance(entity) local pickinfo=PickInfo() if self.entity.world:Pick(self.entity:GetPosition()+Vec3(0,1.6,0),entity:GetPosition()+Vec3(0,1.6,0),pickinfo,0,false,Collision.LineOfSight)==false then if d < self.sightradius then --added so they don't charge from any distance return entity.script end end end end end end function Script:DistanceToTarget() local pos = self.entity:GetPosition() local targetpos = self.target.entity:GetPosition() if math.abs(targetpos.y-pos.y)<1.5 then return pos:xz():DistanceToPoint(targetpos:xz()) else return 100000--if they are on different vertical levels, assume they can't be reached end end function Script:TargetInRange() local pos = self.entity:GetPosition() local targetpos = self.target.entity:GetPosition() if math.abs(targetpos.y-pos.y)<1.5 then if pos:xz():DistanceToPoint(targetpos:xz())<self.attackrange then return true end end return false end function Script:Start() self.entity:SetKeyValue("type","enemy") self.despawnTimer = 0 --Handle default parameters if speed==nil then speed=5.0 end if blendtime==nil then blendtime=500 end if mode==nil then mode=0 end if self.entity:GetMass()==0 then self.entity:SetMass(10) end self.entity:SetPickMode(Entity.BoxPick,true) self.entity:SetPickMode(1,false) self.entity:SetPhysicsMode(Entity.CharacterPhysics) self.entity:SetCollisionType(Collision.Prop,true) self.entity:SetCollisionType(Collision.Character,false) if self.enabled then if self.target~=nil then self:SetMode("roam") else self:SetMode("idle") end end self.sound={} if self.alertsound then self.sound.alert = Sound:Load(self.alertsound) end self.sound.attack={} if self.attack1sound then self.sound.attack[1] = Sound:Load(self.attack1sound) end if self.attack2sound then self.sound.attack[2] = Sound:Load(self.attack2sound) end if self.idlesound then self.sound.idle = Sound:Load(self.idlesound) end self.lastidlesoundtime=Time:GetCurrent()+math.random(1,20000) --Headshot Box self.headshotbox = self.entity:FindChild("Head") -- or whatever you name the head limb in the model tree if self.headshotbox ~= nil then self.headshotbox.script.parent = self.entity self.headshotbox.script.health = self.health self.headshotbox.script.damageMulti = self.headshotDmg end self.onAttackedId = SubscribeEvent("onAttacked", self, self.onAttacked) end function Script:CleanUp() -- (Rick) Unsubscribe(self.onAttackedId) end function Script:Hurt(damage,distributorOfPain) RaiseEvent("onAttacked", { hurtMonsterPosition = self.entity:GetPosition(), player = distributorOfPain}) if self.health>0 then if self.target==nil then self.target=distributorOfPain self:SetMode("attack") end self.health = self.health - (damage + math.random(-4,4)) --local entity = Prefab:Load("Damage numbers/Damage number.pfb") --entity:SetPosition(self.entity:GetPosition(true)+Vec3(0,2,0)) --entity.script:CreateDamageNumber(damage + math.random(-1,3),0.7,size,color,Vec3(0,0.02,0)) if self.health<=0 then self.headshotbox.script.health = 0 self.entity:SetMass(0) self.entity:SetCollisionType(0) self.entity:SetPhysicsMode(Entity.RigidBodyPhysics) self:SetMode("dying") self:DropLoot() RaiseEvent("onDead", { enemyName = self.name.." was killed by player" }) --(Rick) end end end -- if this is the function linked to the onAttacked event function Script:onAttacked(data) System:Print("Yeah it's calling this function") -- I don't recall if this is the correct syntax for distance checks but it gets the idea across. you'll have to research distance checks. --if data.hurtMonsterPosition:GetDistance(self.entity:GetPosition(),true) < 500 then -- if this monster is in a given range of the monster that was attacked then assign this monster the player target as well and it'll come running towards it! self.target = data.player -- end end function Script:EndDeath() self:SetMode("dead") end function Script:DirectMoveToTarget() self.entity:Stop() local targetpos = self.target.entity:GetPosition() local pos = self.entity:GetPosition() local dir = Vec2(targetpos.z-pos.z,targetpos.x-pos.x):Normalize() local angle = -Math:ATan2(dir.y,-dir.x) + self.entity:GetCharacterControllerAngle() + 180.0 self.entity:SetInput(angle,self.speed) end function Script:SetMode(mode) if mode~=self.mode then local prevmode=self.mode self.mode=mode if mode=="idle" then self.target=nil self.entity:PlayAnimation("Idle",0.01) self.entity:Stop()--stop following anything elseif mode=="roam" then if self.target~=nil then self.entity:PlayAnimation("Run",self.animspeedrun) self.entity:GoToPoint(self.target:GetPosition(true),5,5) else self:SetMode("idle") end elseif mode=="attack" then self:EndAttack() elseif mode=="chase" then if self.entity:Follow(self.target.entity,self.speed,self.maxaccel) then if prevmode~="chase" then if self.sound.alert then self.entity:EmitSound(self.sound.alert) end end self.followingtarget=true self.entity:PlayAnimation("Run",self.animspeedrun,300) if self:DistanceToTarget()<self.attackrange*2 then self.followingtarget=false self.entity:Stop() self:DirectMoveToTarget() end else self.target=nil self:SetMode("idle") return end elseif mode=="dying" then self.entity:Stop() self.entity:PlayAnimation("Death",self.animspeedDeath,300,1,"EndDeath") elseif mode=="dead" then if self.mode == "dead" then self.entity:SetCollisionType(0) self.entity:SetShape(nil) self.entity:SetPhysicsMode(Entity.RigidBodyPhysics) self.enabled=false end end end end function Script:EndAttack() if self.mode=="attack" then if self.target==nil then self:SetMode("idle") return end if self.target.health<=0 then self:SetMode("idle") return end local d = self:DistanceToTarget() if d>self.attackrange then self:SetMode("chase") return end self.entity:Stop() self.attackmode = 1-self.attackmode--switch between right and left attack modes self.entity:PlayAnimation("Attack"..tostring(1+self.attackmode),0.07,500,1,"EndAttack") self.attackbegan = Time:GetCurrent() if self.sound.attack[self.attackmode+1] then if math.random()>0.75 then self.entity:EmitSound(self.sound.attack[self.attackmode+1]) end end end end function Script:UpdatePhysics() if self.enabled==false then return end local t = Time:GetCurrent() self.entity:SetInput(self.entity:GetRotation().y,0) if self.sound.idle then if t-self.lastidlesoundtime>0 then self.lastidlesoundtime=t+20000*Math:Random(0.75,1.25) self.entity:EmitSound(self.sound.idle,20) end end if self.mode=="idle" then if t-self.lastupdatetargettime>250 then self.lastupdatetargettime=t self.target = self:ChooseTarget() if self.target then self:SetMode("chase") end end elseif self.mode=="roam" then if self.entity:GetDistance(self.target)<1 then self:SetMode("idle") end elseif self.mode=="chase" then if self.target.health<=0 then self:SetMode("idle") return end if self:TargetInRange() then self:SetMode("attack") elseif self:DistanceToTarget()<self.attackrange*2 then self.followingtarget=false self.entity:Stop() self:DirectMoveToTarget() else if self.followingtarget==false then if self.entity:Follow(self.target.entity,self.speed,self.maxaccel) then self:SetMode("idle") end end end elseif self.mode=="attack" then if self.attackbegan~=nil then if t-self.attackbegan>self.attackdelay then if self.target.entity:GetDistance(self.entity)<1.5 then self.attackbegan=nil self.target:Hurt(self.damage) end end end local pos = self.entity:GetPosition() local targetpos = self.target.entity:GetPosition() local dx=targetpos.x-pos.x local dz=targetpos.z-pos.z if self.entity:GetCharacterControllerAngle()>90.0 then self.entity:AlignToVector(-dx,0,-dz) else self.entity:AlignToVector(dx,0,dz) end end end function Script:UpdateWorld() if self.enabled == true then self.despawnTimer = self.despawnTimer + Time:GetSpeed()/100 if self.despawnTimer > self.despawnTime then self.mode = "dead" self.entity:Hide() self.script = nil end end if self.mode == "dead" then self.removeBodyTimer = self.removeBodyTimer + (Time:GetSpeed()/100) if (self.removeBodyTimer > self.removeBodyTime) then self.entity:Hide() if self.entity:Hide() then self.entity:Release() self.script = nil end end end end function Script:DropLoot() self.inventory = {} self.inventory[1] = "HUD Elements/Inventory/Prefabs/Health.pfb" self.inventory[2] = "HUD Elements/Inventory/Prefabs/Stamina.pfb" self.inventory[3] = "HUD Elements/Inventory/Prefabs/Shield.pfb" self.inventory[4] = "HUD Elements/Inventory/Prefabs/Health.pfb" self.inventory[5] = "HUD Elements/Inventory/Prefabs/Stamina.pfb" self.inventory[6] = "HUD Elements/Inventory/Prefabs/Shield.pfb" self.gun = {} self.gun[1] = "AddOns/FPS Weapons Pack/pickup mp5.pfb" self.gun[2] = "AddOns/FPS Weapons Pack/pickup pistol.pfb" self.gun[3] = "AddOns/FPS Weapons Pack/pickup shotgun.pfb" self.gun[4] = "AddOns/FPS Weapons Pack/pickup m4.pfb" self.gun[5] = "AddOns/FPS Weapons Pack/pickup machete.pfb" self.ammo = {} self.ammo[1] = "Prefabs/Ammo Prefabs/9 ammo Hover.pfb" self.ammo[2] = "Prefabs/Ammo Prefabs/Combat Rifle Hover (5).pfb" self.ammo[3] = "Prefabs/Ammo Prefabs/mp5 ammo Hover (4).pfb" self.ammo[4] = "Prefabs/Ammo Prefabs/Shotgun Shells Hover(3).pfb" math.randomseed(Time:Millisecs()) num = math.random(1, 100) if num >= 0 and num < 15 then local spawn = Prefab:Load(self.inventory[math.random(1,6)]) local SpawnPos = self.entity:GetPosition() + Vec3(0,1.5,0) spawn:SetPosition(SpawnPos) return elseif num >= 15 and num <= 20 then local spawn = Prefab:Load(self.gun[math.random(1,5)]) local SpawnPos = self.entity:GetPosition() + Vec3(0,1.5,0) spawn:SetPosition(SpawnPos) return elseif num > 20 and num <= 40 then local spawn = Prefab:Load(self.ammo[math.random(1,4)]) local SpawnPos = self.entity:GetPosition() + Vec3(0,1.5,0) spawn:SetPosition(SpawnPos) return end end
  6. Yeah it's not even getting that far. I keep getting the same error.
  7. Oh yeah. That's weird. Because it's written correctly in the code. This is all the code I added. It's all in the crawler script: function Script:Start() ... self.onAttackedId = SubscribeEvent("onAttacked", self, self.onAttacked) --subscribed to event end function Script:CleanUp() -- (Rick) Unsubscribe(self.onAttackedId) --unsubscribed to event end function Script:Hurt(damage,distributorOfPain) if self.health>0 then player = self.target RaiseEvent("onAttacked", { hurtMonsterPosition = self.entity:GetPosition(true), player = distributorOfPain }) --- raise event ... -- if this is the function linked to the onAttacked event function Script:onAttacked(data) --raise event function -- I don't recall if this is the correct syntax for distance checks but it gets the idea across. you'll have to research distance checks. if data.hurtMonsterPosition:GetDistance(self.entity:GetPosition(),false) < 5 then -- if this monster is in a given range of the monster that was attacked then assign this monster the player target as well and it'll come running towards it! self.target = data.player end end In the original kills thing you you showed me you wrote this: function Script:Start() self.onDeadId = SubscribeEvent("onDead", self, self.enemyDied) ...I was trying to figure out where you got "self.onDeadId" I just tried to mimic that in the crawler script. I also tried setting "player = self.target" How would it know what "player" means in the Hurt()? I'm getting there slowly.
  8. I'm getting "attempt to index a nil value" on this line in the event system script: local scriptFunc = events[eventName].scriptFunction I had added these to the crawler script: function Script:Start() self.onAttackedId = SubscribeEvent("onAttacked", self, self.onAttacked) ... function Script:CleanUp() -- (Rick) Unsubscribe(self.onAttackedId) end Just tried to mimic what you did before.
  9. Ah ok. Hold on let me see if I can figure that out.
  10. That one doesn't seem to be doing anything. I have it set up in the crawler script like: function Script:Hurt(damage,DistributorofPain) RaiseEvent("onAttacked", { hurtMonsterPosition = self.entity:GetPosition(true), player = distributorOfPain }) -- the script doesn't reference "player" anywhere else. Is it that? -- if this is the function linked to the onAttacked event function Script:onAttacked(data) -- I don't recall if this is the correct syntax for distance checks but it gets the idea across. you'll have to research distance checks. if data.hurtMonsterPosition:GetDistance(self.entity:GetPosition(),true) < 500 then -- if this monster is in a given range of the monster that was attacked then assign this monster the player target as well and it'll come running towards it! self.target = data.player end end
  11. This is cool, man. It's totally working. In the crawler script I set it like this: RaiseEvent("onDead", { enemyName = self.name.." was killed by player" }) and in the player script under enemyDied(data) like you said: self.killMessage = data.enemyName What's another event that this would be good for? I want to try and see if I can follow the pattern and piece one together myself. I'm going to also see if I can get the names to list downward with a table iteration. That ought to keep me going for awhile.
  12. Ok cool! Because I tend to have a lot of questions. I totally want to learn this now that I'm seeing what it can do. This is like... the future lol. I think I get this: " One event string name can have many different function callbacks linked to it... before we added coroutines to this all, those callback functions were just be called right there... Now inside raise event instead of looping through and calling the function, we loop through and get the function for the raised event string name and create a coroutine variable from it and store it in a separate table." So basically the event system grabs the information by the string name and puts it into a table so it can loop everything, runs it through that extra loop within itself, adding whatever you want to it, before passing it back into its original loop? So basically this just pulls out a piece of information and stores it, sends it around an outside loop, then puts it back? Like a track that you switch so the train (string name) always makes an extra stop? Here's the parts that I have. I'm not getting anything currently. I'm not sure what you mean by "render2D function"? I'm doing the drawtext in the player script under the PostRender function. In the player script: function Script:enemyDied(data) self.kills = self.kills + 1 WaitForSeconds(2.5); self.killMessage = "" ---do I write the context:DrawText(...) here? WaitForSeconds(1.0); while self.killMessageAlpha >= 0 do self.killMessageAlpha = self.killMessageAlpha - .01 WaitForSeconds(0.25) self.killMessage = "" self.killMessageAlpha = 1 end end function Script:CleanUp() Unsubscribe(self.onDeadId) Unsubscribe(self.killMessage) end --This function will be called when an entity is loaded in a map. Use this for intitial setup stuff. function Script:Start() self.onDeadId = SubscribeEvent("onDead", self, self.enemyDied) self.killMessage = "" self.killMessageAlpha = 1 ... ...and later in the PostRender just: --draw kills context:SetBlendMode(1) context:SetColor(1,1,1,1) context:DrawText("Kills: "..self.kills,30,30,200,200) --- Do I need to say "self.killMessage = " here first? Edit: My bad, this is just the kills. But is it good write the other text here? ... I changed the name of my "Coroutine" script to "Event System" and did like you said in the Main.lua just added "BackLoop()" between the Time and World update. That seems to be working. And my crawler script still looks the same. Do I need to add something there? or I guess this happens in the same "onDead" event. function Script:Hurt(damage,distributorOfPain) ... if self.health<=0 then self.entity:SetMass(0) self.entity:SetCollisionType(0) self.entity:SetPhysicsMode(Entity.RigidBodyPhysics) self:SetMode("dying") RaiseEvent("onDead", {}) end end end
  13. Dude this is like chaos to me. Probably because it's coming all at once. I'm having a hard time seeing the process from beginning to end in a coherent line. I want to grasp it because I can see that it gives you a lot of control over events and sequences of events and I assume it's a more "industry-standard" way to do things. I looked up "callback" and it kind of makes sense that its passing a function as a variable from one "system" to another, which then executes (or "calls back") that function within the limits that you put on it in the parent function. So when you "subscribe" to an event the coroutine basically just grabs it or gets involved in the process, tells it to do some things, and then "unsubscribes" from it. It's vaguely familiar how you're passing information around in parentheses and defining them somewhere else because I've seen some of that in the shaders. Maybe if you have the time (and/or patience) you could walk me through how I could use this to use context:DrawText() to put "enemy killed" on the screen instead of in the System:Print(). Would that be easy? That was something I was going to try to figure out after I got the kill-counter working and I was trying to think of how I could set it when the enemy is killed and then wait a few seconds and then delete it, and it seems like this may be the way to do that. Eventually I want to set it up to say something like "entity.name.." was killed by player" and then set that in a table that iterates each name so I might have several names on the screen at one time (basically like a list of recent kills). I can maybe figure out that last part on my own later. But like what's step one? I made another script called "Coroutine" (should I call it that?) for that part that I put in the Main.lua, and then in the main put " import("Scripts/Coroutine.lua") but I left this part because I wasn't sure how to import it into that specific place. -- loop over backwards so we can safely remove event function coroutines that are finished for i = #eventCoroutines, 1, -1 do if coroutine.status(eventCoroutines.co) == "dead" then table.remove(eventCoroutines, i) else -- go back into the event function passing the script as the first param so it ends up being 'self' inside the function and args as the second parameter coroutine.resume(eventCoroutines.co, eventCoroutines.script, eventCoroutines.args) end end This is the idea:
  14. I forgot I had pasted in those other two functions. I combined the two "enemyDied(data)" and it works now: function Script:enemyDied(data) self.kills = self.kills + 1 WaitForSeconds(2.5); System:Print("Enemy died") WaitForSeconds(1.0); System:Print("Wow this is cool!") end
  15. So that's like how in the video you put a function inside a function.
×
×
  • Create New...