Need help making a Redead AI that acts similar to Ocarina of time

Started by Akamatsu, September 09, 2018, 05:05:48 PM

Previous topic - Next topic
Make sure you start you code with <code=lua>, not just <code> (square brackets, not <> signs.) This adds line numbers, which is SUPER helpful if I want to tell you there's an issue on line 19. Which there is. At least, I think it's line 19, but there aren't any line numbers, so....

Also, preface. You're clearly just leaning, and trying to take other people's code and make it work and don't know 100% what's going on yet, and that's okay. This actually seems like a pretty great code, especially for a beginner. You've just made a few beginner mistakes. But I think the ideas behind the code, that there should be an enemy that wakes up and starts playing miniboss music when he's first hit, and that once he takes some damage, he is replaced by an unarmored version, is a good concept. I assume the code for this basically works? It seems like it mostly should.

But, there's a couple issues and I'm going to give you a lot of information, hopefully it will help you learn and the next script you make, will be better because you'll know why you're making certain choices (not just "because it works").



So,
I don't think "sleeping" should be a savegame value- for one thing, right now every Iron Knuckle in the game shares the same value if they're sleeping or not so they'd all wake up at once XD. That's just a mess. What you want is a local variable.

For example, "local sprite" and "local movement" are two local variables called sprite and movement. They're declared at the top of the script, but they don't have any information in them yet. Some code will put some information in this variable later in the script, and it will affect only this script. Local variables are local to the "scope" of wherever you create them. So, since they're declared just in the enemy script, they're only a part of the enemy script. Every enemy can have a local variable called "number_of_pineapples" and if you give a specific enemy six pineapples when he bumps into a pineapple tree, that will just give him six pineapples. If you have an enemy with code like
Code (lua) Select
enemy:on_bumping_into_tree()
  game:set_value("number_of_pineapples", 6)
end

Then EVERY enemy of that breed would get six pineapples. You'd want:
Code (lua) Select

local pineapples
enemy:on_bumping_into_tree()
  pineapples = pineapples+6
end


Then the pineapples variable is just accessible by this script, not ANY script looking at game:get_value("pineapples")
So I think you'd want "sleeping" to be a similar way, where each instance of the script running has a local sleeping variable. Does that all make sense?


And speaking of local variables, you have these lines:
local sprite
local sprite = enemy:create_sprite("enemies/" .. enemy:get_breed())
This is redundant. It's like saying "I'm going to create a local variable, and it's going to be called sprite. I'm going to create a local variable, and it's going to be called sprite, and it's going to contain [the sprite of this enemy breed]". You definitely shouldn't be declaring variables of any type twice.

Better than that would be:
local sprite
sprite = enemy:create_sprite("enemies/" .. enemy:get_breed())
But that's still redundant.

The best option would be
local sprite = enemy:create_sprite("enemies/" .. enemy:get_breed())



Moving on down, your line "enemy:get_damage()" is getting the value you just set with "enemy:set_damage()", but... it's not doing anything at all with that value? You just get it and then ignore it and move on. It's like if in the middle of a paragraph I just wrote something unrelated. 12. And then kept going and never talked about it again.

I still can't figure out why self:set_pushed_back_when_hurt(false) needs to be self and not enemy. Maybe this is a bug? Someone smarter than me will have to help you there : /


Now, tell me if I'm wrong, but does sleeping = 1 mean it's asleep and sleeping = 2 mean it's awake?
If that's the case, you should use a boolean variable. Booleans can only be true or false (or in lua, nil, which acts a lot like false but basically means nobody ever bothered assigning it a value).
Basically, instead of saying something like
Code (lua) Select

local sleeping = 1

You would say
Code (lua) Select

local sleeping = true


Then you could have statements like
Code (lua) Select

if sleeping == true then
  movement = sol.movement.create("target")
end


True/False is a LOT easier to understand than 1 or 2 for something that can only be true or false, like "is this enemy asleep?"



Finally, you have this mess here:
Code (lua) Select

if game:get_value("Sleeping") == 2
then movement = sol.movement.create("target")
end
if game:get_value("Sleeping") == 2
then movement:set_target(hero)
end
if game:get_value("Sleeping") == 2
then movement:set_speed(15)
end
if game:get_value("Sleeping") == 2
then movement:start(enemy)
end
if game:get_value("Sleeping") == 2
then sprite:set_animation("walking")
end


pleasepleasepleasepleaseplease write that like this:
Code (lua) Select

if game:get_value("Sleeping") == 2 then
  movement = sol.movement.create("target")
  movement:set_target(hero)
  movement:set_speed(15)
  movement:start(enemy)
  sprite:set_animation("walking")
end


Your way technically isn't wrong, but... it's very wrong.




I happen to have just started taking a computer coding course and the importance of writing code other people can understand is something we covered today. So we were encouraged to share our code and read other people's code, so this is practically studying for me, lol.

Okay,

Thank you so much for your help the reason I wrote

Code (lua) Select
if game:get_value("Sleeping") == 2

for each one is because for whatever reason it was giving me errors if I tried to group them under one .

no clue why it did this for me maybe I just did it wrong not 100% sure

either way thanks for the tips I learned coding on other engines but I have not really messed with lua before so it's still kind of new to me.

ah I figured out why it was giving errors I kind of feel dumb now I was putting code like this

Code (lua) Select
if game:get_value("Sleeping") == 2
then movement:set_target(hero)
then movement:set_target(hero)
then movement:set_target(hero)
end


I actually had no idea how to use the local function this is basically the same thing as a reference from elder scrolls engine's I was wondering if their was a way to use this type of feature but I just didn't understand how to declare it this helps me out VERY much seriously thanks for explaining this will make things much smoother  :D

Yeah, most of my mistakes are me being like, why won't this work, I don't understand anything! And it's some simple syntax error like that.

I googled the elder scrolls engine, did you use their creation kit thing to make mods with their papyrus language?
Papyrus Introduction

If so, from my brief glance over this page, I bet you'll find a lot of ideas to be pretty familiar in Solarus, just worded differently.


Tip: you should improve the indentation of your code, to make it more readable. And read the scripts of finished projects to learn the basics.
"If you make people think they're thinking, they'll love you. But if you really make them think, they'll hate you."

I've been doing that since I started.
I'm going to put my redead code here just in case somebody is looking for one and since you guys helped me it's only fair

I cleaned it up last night and made it look nicer your welcome to look over it and point anything out that may have been done wrong or could have been done better but it works like it should doesn't mean it couldn't be improved.

Code (lua) Select

local enemy = ...
local game = enemy:get_game()
local map = enemy:get_map()
local hero = map:get_hero()
local sprite = enemy:create_sprite("enemies/Redead2")
local movement
local IsPlayerFrozen = false


function enemy:on_created()
movement = sol.movement.create("target")
  enemy:set_life(8)
  enemy:set_damage(10)
end





function enemy:on_restarted()
  sol.timer.start(self, 3000, function() sol.audio.play_sound("Redead/RedeadMoan") end)
  self:get_sprite():set_animation("immobilized")
  enemy:check_hero()
if self:get_distance(hero) <= 80 then
if IsPlayerFrozen == false then
  sol.timer.start(self, 3000, function() hero:freeze() end)
  IsPlayerFrozen = true
  sol.audio.play_sound("Redead/RedeadScream")
end
end
  if self:get_distance(hero) <= 80 then
  if IsPlayerFrozen == true then
  sol.timer.start(self, 5000, function() hero:unfreeze() end)
  IsPlayerFrozen = false
end
end
end



function enemy:check_hero()
  if self:get_distance(hero) <= 80 then
  movement = sol.movement.create("target")
  self:get_sprite():set_animation("walking")
  movement:set_speed(28)
  movement:start(enemy)
end
sol.timer.start(self, 1000, function() self:check_hero() end)
end








function enemy:on_dying()
sol.audio.play_sound("Redead/RedeadDie")
end


I'm going to reformat your code a little bit, like Diarandor suggested.

I also added some comments into your code. One big this is don't do something like:
if rupees <5 then
  some code
end
if rupees > 5 then
  some code
end

You should be using an if/then/else/end construction.
https://www.lua.org/pil/4.3.1.html
Look over that page. Also, elseif conditions are very useful.

Code (lua) Select

local enemy = ...
local game = enemy:get_game()
local map = enemy:get_map()
local hero = map:get_hero()
local sprite = enemy:create_sprite("enemies/Redead2")
local movement
local IsPlayerFrozen = false


function enemy:on_created()
  movement = sol.movement.create("target")
  enemy:set_life(8)
  enemy:set_damage(10)
end



function enemy:on_restarted()
  sol.timer.start(self, 3000, function() sol.audio.play_sound("Redead/RedeadMoan") end)
--do you really want every redead on the map to moan 3 seconds after you enter the map?
  self:get_sprite():set_animation("immobilized")
  enemy:check_hero()
--you will probably want your check_hero() method to freeze the player. As it is, if the hero is more than 80px away, the redead will not
--freeze the hero, but then it doesn't loop. It won't try to freeze the hero again until you damage it. I think you probably want to
--check if the hero is close enough to freeze constantly, or every second or so, which is what your check_hero() method is doing anyway.

  if self:get_distance(hero) <= 80 then
    if IsPlayerFrozen == false then
      sol.timer.start(self, 3000, function() hero:freeze() end)
      IsPlayerFrozen = true
      sol.audio.play_sound("Redead/RedeadScream")
    end
  else --instead of checking a condition multiple times, do an "if player isn't close", then "ELSE", which mean, if the play IS close in this case.
      sol.timer.start(self, 5000, function() hero:unfreeze() end)
      IsPlayerFrozen = false
    end
  end
end



function enemy:check_hero()
  if self:get_distance(hero) <= 80 then
    movement = sol.movement.create("target")
    self:get_sprite():set_animation("walking")
    movement:set_speed(28)
    movement:start(enemy)
  end
  sol.timer.start(self, 1000, function() self:check_hero() end)
end



function enemy:on_dying()
  sol.audio.play_sound("Redead/RedeadDie")
end

can you explain a bit more for some reason if I make it else the code doesn't work anymore it just runs it once instead of looping it for some reason.

It is a bit risky to put the unfreezing timer on the enemy (the enemy could be killed when hero is frozen, for instance, by a boomerang, or other things). I recommend to put that timer (and maybe others) on the map, and get the frozen state with: hero:get_state() == "frozen".
"If you make people think they're thinking, they'll love you. But if you really make them think, they'll hate you."

I'm not sure which part of your code was looping? The only part that should have looped was the check_hero() method, which loops every 1sec. That's why I suggested putting the freezing function in there.

On their own, if/else branches don't loop. They have to be inside a loop for that.

Briefly: explain, this: "the code doesn't work anymore it just runs it once".
"If you make people think they're thinking, they'll love you. But if you really make them think, they'll hate you."

Not, sure what their is to explain?

if I use the else feature instead of making another if statement it no longer loops like it does before I changed it.

Basically the hero no longer gets unfrozen and frozen more then once.

You are right that the code is not ok. And there are a few more things wrong with the last code of Max, but if you read carefully, you can find them (exercise!). You will need to move/rewrite a few things to fix it. I challenge you, Akamatsu, to fix it!

[insert samurai emoticon here]

Edit: I think Max had in mind a different behavior for the Zombi thing, without restarting the freezing feature, or he wrote it too fast.
"If you make people think they're thinking, they'll love you. But if you really make them think, they'll hate you."

it was already fixed before I tried to change it  :D I just changed it back although I kind of have a rough idea of what was wrong with it.

so enemies do not play walking left and right animations by default?