Developing a RPG Engine for Renpy

Discuss how to use the Ren'Py engine to create visual novels and story-based games. New releases are announced in this section.
Forum rules
This is the right place for Ren'Py help. Please ask one question per thread, use a descriptive subject like 'NotFound error in option.rpy' , and include all the relevant information - especially any relevant code and traceback messages. Use the code tag to format scripts.
Message
Author
User avatar
hyperionthunder
Regular
Posts: 51
Joined: Fri May 18, 2018 2:10 pm
Projects: The Wanderers
Deviantart: hyperionthunder
itch: hyperionthunder
Contact:

Re: Developing a RPG Engine for Renpy

#31 Post by hyperionthunder »

I am still stuck. What I thought about doing is using a global variable to hold the values retrieved from Image Bound function

This is from battle.rpy

Code: Select all

global target_x
global target_y
global target_w
global target_h
#battle_target is the player's chosen target
target_x, target_y, target_w, target_h = renpy.get_image_bounds(battle_target.image)
renpy.show_screen("battle_message", load_skill.name) #show the skill name
renpy.show(load_skill.sprite) #in this case "battle_fire_alpha" is the sprite name, stored in AbilityCodes class I created.
This is from image.rpy
now when the image sprite is displayed:

Code: Select all

image battle_fire_alpha:
    "battle/effects/fire-a-sprite.png"
    on show:
        battle_ani_fire_alpha()
I had the image automatically trigger the transform on show
The following functions I have written to capture the global variable and pass it into the transform the image called.

This is from battle_animation.rpy

Code: Select all

init python:
    def adjust_x():
        global target_x # the variable set earlier by the battle.rpy.
        global target_w
        x = target_x + (target_w / 2)
        return x
    def adjust_y():
        global target_y
        global target_h
        y = target_y + (target_h / 2)
        return y

transform battle_ani_fire_alpha:
        xpos adjust_x()
        xpos adjust_y()
        alpha 0.0
        zoom 1
        parallel:
            pause 0.5
            linear 0.2 zoom 0.6
        parallel:
            linear 0.5 alpha 1.0
            pause 0.2
            linear 0.3 alpha 0.0
am I on the right track here? What I am trying to do is get that location of the battle_target on the screen, store it as a global variable, then retrieve it from renpy store by a python function called by the transform to set the position of the animation to be displayed...

User avatar
hyperionthunder
Regular
Posts: 51
Joined: Fri May 18, 2018 2:10 pm
Projects: The Wanderers
Deviantart: hyperionthunder
itch: hyperionthunder
Contact:

Re: Developing a RPG Engine for Renpy

#32 Post by hyperionthunder »

I figured out how to make it work, using the thought process i had so far.

now i found an interesting bug when using targeting all enemies with an ability:
here's the script i wrote to target all and hit each of them:

Code: Select all

elif load_skill.range == "all":
     #hits all of them
     for battle_target in encounter_list[monsterparty].enemies:
          target_x, target_y, target_w, target_h = renpy.get_image_bounds(battle_target.image)
          renpy.show_screen("battle_message", load_skill.name)
          renpy.show(load_skill.sprite)
          renpy.pause(1)
          renpy.hide(load_skill.sprite)
          if battle_target.defend:
               #damage is halved if enemy is defending.
               if load_skill.element == "Physical":
                    ability_attack = renpy.random.randint(load_skill.mindamage, load_skill.maxdamage) + member.str
               else:
                    ability_attack = renpy.random.randint(load_skill.mindamage, load_skill.maxdamage) + member.int
               damage = (ability_attack - battle_target.defense) / 2
          else:
               if battle_target.weak == load_skill.element:
                    ##do double damage if not defending and is weak against that element
                    renpy.show_screen("battle_message", "Critical Damage!")
                    renpy.with_statement(Fade(0.01,0.01,0.5, color="%s" % load_skill.color))
                    if load_skill.element == "Physical":
                         ability_attack = renpy.random.randint(load_skill.mindamage, load_skill.maxdamage) + member.str
                    else:
                         ability_attack = renpy.random.randint(load_skill.mindamage, load_skill.maxdamage) + member.int
                    damage = 3 * ability_attack - battle_target.defense
               else:
                    if load_skill.element == "Physical":
                         ability_attack = renpy.random.randint(load_skill.mindamage, load_skill.maxdamage) + member.str
                    else:
                         ability_attack = renpy.random.randint(load_skill.mindamage, load_skill.maxdamage) + member.int
                    damage = ability_attack - battle_target.defense
          battle_target.hp -= damage
          renpy.show_screen("battle_dialogue","%s casts %s and hits %s for %s points of damage.\nTarget has %s HP left." % (member.name, load_skill.name, battle_target.image, damage, battle_target.hp))
          renpy.pause(2)

     for battle_target in encounter_list[monsterparty].enemies:
          if battle_target.hp < 1:
               #target died, remove him from the fight
               renpy.play("sound/battle-enemy-death.ogg")
               renpy.show_screen("battle_dialogue", "%s has died." %battle_target.name)
               renpy.show(battle_target.image, at_list=[battle_monsterDeath(battle_target.image)])
               renpy.pause(2)
               renpy.hide(battle_target.image)
               # remove enemy from encounter list
               renpy.hide_screen("battle_dialogue")
               encounter_list[monsterparty].enemies.remove(battle_target)
The code works and when i had an attack that kills all the enemies, in this case 3 spiders, it killed and removed only the 1st and 3rd spider, leavint the 2nd spider on the screen even if it ran out of HP. I am trying to figure out what was the bug.

kivik
Miko-Class Veteran
Posts: 786
Joined: Fri Jun 24, 2016 5:58 pm
Contact:

Re: Developing a RPG Engine for Renpy

#33 Post by kivik »

The logic of the code makes sense to me. When you said 2nd spider ran out of HP, did the battle_dialogue screen say Target has 0 HP left? Can you add this statement in your code and use the Dev console (shift+O) after the finishing blow to see what may have happened?

Code: Select all

     for battle_target in encounter_list[monsterparty].enemies:
          # your indentation isn't block of 4 here btw!
          print("%s has %s HP left." % (battle_target.name, battle_target.hp))
          # ^ I assumed battle_target.name is valid, use whatever attribute the name is stored at
          if battle_target.hp < 1:
Alternatively:

Code: Select all

     for battle_target in encounter_list[monsterparty].enemies:
          renpy.show_screen("battle_dialogue","%s has %s HP left." % (battle_target.name, battle_target.hp))
          renpy.pause(2)
          if battle_target.hp < 1:
This will use your battle dialogue screen and tell you each monster's health.

User avatar
hyperionthunder
Regular
Posts: 51
Joined: Fri May 18, 2018 2:10 pm
Projects: The Wanderers
Deviantart: hyperionthunder
itch: hyperionthunder
Contact:

Re: Developing a RPG Engine for Renpy

#34 Post by hyperionthunder »

Hmm, when it does the for battle_target in encounter_list[monsterparty].enemies:
message displays (enemy name, 1, hp left)
first enemy is checked,
hp is lower than 0, so it is removed via animation then
it does encounter_list[monsterparty].enemies.remove(battle_target) <-- i wonder if that one is causing the list to shorten and this skips number 2?

when it goes through the 2nd entry, it skipped over the 2nd monster and went for 3rd monster instead.
I wonder if the way it handles the list update messed with the count?

User avatar
hyperionthunder
Regular
Posts: 51
Joined: Fri May 18, 2018 2:10 pm
Projects: The Wanderers
Deviantart: hyperionthunder
itch: hyperionthunder
Contact:

Re: Developing a RPG Engine for Renpy

#35 Post by hyperionthunder »

wait if it removed enemy number 1, it caused enemy number 2 to move to slot 1, and enemy number 3 to slot 2, causing enemy 2 to be skipped over on the next pass... that's prolly what's causing the bug?

How can I reset the for loop to correctly check the list again?

User avatar
hyperionthunder
Regular
Posts: 51
Joined: Fri May 18, 2018 2:10 pm
Projects: The Wanderers
Deviantart: hyperionthunder
itch: hyperionthunder
Contact:

Re: Developing a RPG Engine for Renpy

#36 Post by hyperionthunder »

I found a workaround by creating a separate list to list the enemies that remains alive then equal it back to the battle_target in encounter_list[monsterparty].enemies, this preserving the run through of each enemies and then if they are dead, do not add them to the new list.

Code: Select all

#creates a placeholder list to house surviving enemies
battle_adjust_list = []
for battle_target in encounter_list[monsterparty].enemies:
	renpy.show_screen("battle_dialogue","%s (%s) has %s HP left." % (battle_target.name, battle_target.image, battle_target.hp))
	renpy.pause(2)
	if battle_target.hp <= 0:
		#target died, remove him from the fight
		renpy.play("sound/battle-enemy-death.ogg")
		 renpy.show_screen("battle_dialogue", "%s has died." %battle_target.name)
		renpy.show(battle_target.image, at_list=[battle_monsterDeath(battle_target.image)])
		renpy.pause(2)
		renpy.hide(battle_target.image)
		renpy.hide_screen("battle_dialogue")
	else:
		#target that is still alive is added
		battle_adjust_list.append(battle_target)
#updates the monster encounter list with members that are still alive.
encounter_list[monsterparty].enemies = battle_adjust_list


User avatar
Remix
Eileen-Class Veteran
Posts: 1628
Joined: Tue May 30, 2017 6:10 am
Completed: None... yet (as I'm still looking for an artist)
Projects: An un-named anime based trainer game
Contact:

Re: Developing a RPG Engine for Renpy

#37 Post by Remix »

Yes, altering a list during iteration is often a gotcha approach.

In most similar circumstances, people tend toward iterating backwards if they can...

Code: Select all

for k in my_list.reverse():
    if condition: 
        my_list.remove(k)
This *can* also gotcha though as .remove just removes the first occurrence, so index based...

Code: Select all

for i in range( len(my_list)-1, -1, -1 ):
    if condition:
        del my_list[i]
Or use a reference list like you have done
Frameworks & Scriptlets:

User avatar
hyperionthunder
Regular
Posts: 51
Joined: Fri May 18, 2018 2:10 pm
Projects: The Wanderers
Deviantart: hyperionthunder
itch: hyperionthunder
Contact:

Re: Developing a RPG Engine for Renpy

#38 Post by hyperionthunder »

That's a good suggestion too. I'll give it a try down the road.

User avatar
hyperionthunder
Regular
Posts: 51
Joined: Fri May 18, 2018 2:10 pm
Projects: The Wanderers
Deviantart: hyperionthunder
itch: hyperionthunder
Contact:

Re: Developing a RPG Engine for Renpy

#39 Post by hyperionthunder »

but my current solution allows me to preserve the original encounter list, so i can repeat it.

User avatar
hyperionthunder
Regular
Posts: 51
Joined: Fri May 18, 2018 2:10 pm
Projects: The Wanderers
Deviantart: hyperionthunder
itch: hyperionthunder
Contact:

Re: Developing a RPG Engine for Renpy

#40 Post by hyperionthunder »

I have run into a weird bug that I am trying to pin it down. It only appears when someone loads a save.
I get this:

Code: Select all

I'm sorry, but an uncaught exception occurred.

While running game code:
  File "game/script-chapter-02-day-06.rpy", line 271, in script call
    call dungeon_fd_garden_maze from _call_dungeon_fd_garden_maze
  File "game/dungeon_fd_garden_maze.rpy", line 212, in script
    $ dungeon_battle(choose_encounter)
  File "game/dungeon_fd_garden_maze.rpy", line 212, in <module>
    $ dungeon_battle(choose_encounter)
  File "game/battle.rpy", line 536, in dungeon_battle
    renpy.show_screen("battle_message", load_skill.name)
AttributeError: 'AbilityCode' object has no attribute 'sprite'

-- Full Traceback ------------------------------------------------------------

Full traceback:
  File "game/script-chapter-02-day-06.rpy", line 271, in script call
    call dungeon_fd_garden_maze from _call_dungeon_fd_garden_maze
  File "game/dungeon_fd_garden_maze.rpy", line 212, in script
    $ dungeon_battle(choose_encounter)
  File "C:\renpy\renpy\ast.py", line 862, in execute
    renpy.python.py_exec_bytecode(self.code.bytecode, self.hide, store=self.store)
  File "C:\renpy\renpy\python.py", line 1912, in py_exec_bytecode
    exec bytecode in globals, locals
  File "game/dungeon_fd_garden_maze.rpy", line 212, in <module>
    $ dungeon_battle(choose_encounter)
  File "game/battle.rpy", line 536, in dungeon_battle
    renpy.show_screen("battle_message", load_skill.name)
AttributeError: 'AbilityCode' object has no attribute 'sprite'

Windows-8-6.2.9200
Ren'Py 7.0.0.196
The Wanderers 0.93
Sun Jun 10 14:01:16 2018
The code is

Code: Select all

elif action == "ability":
	renpy.show_screen("battle_message", "Choose Your Ability Code")
	renpy.call_screen("battle_skills", member)
	renpy.show_screen("battle_message", "Choose Your Target")
	#target and range check
	if load_skill.target == "enemy":
		#damages enemy
		if load_skill.range == "single":
			#hit one enemy
			renpy.call_screen("enemy_target", monsterparty)
			target_x, target_y, target_w, target_h = renpy.get_image_bounds(battle_target.image)
			renpy.show_screen("battle_message", load_skill.name)
			renpy.show(load_skill.sprite)
			renpy.pause(1)
			renpy.hide(load_skill.sprite)
To verify, i had the following written in my AbilityCode class

Code: Select all

class AbilityCode:
        def __init__(self, name, desc, icon, unequipicon, mindamage, maxdamage, minheal, maxheal, cpcost, hpcost, element, elementicon, target, range, status, sprite, color, found, locked="locked_ac"):
            self.name = name
            self.desc = desc
            self.icon = icon
            self.unequipicon = unequipicon
            self.mindamage = mindamage
            self.maxdamage = maxdamage
            self.minheal = minheal
            self.maxheal = maxheal
            self.cpcost = cpcost
            self.hpcost = hpcost
            self.element = element
            self.elementicon = elementicon
            self.target = target
            self.range = range
            self.status = status
            self.sprite = sprite
            self.color = color
            self.found = found
            self.locked = locked
The ability being called is:

Code: Select all

ability_codes_list["24"] = AbilityCode("Code: Earth α", "A tiny ball of stone.", "code_earth_a_on", "code_earth_a_off", 20, 30, 0, 0, 3, 0, "Earth", "element_earth", "enemy", "single", "none", "battle_earth_alpha", "#ff0", True)
I am little confused with the way Renpy handles the class as all the attributes are present yet it... isn't?
Help!

kivik
Miko-Class Veteran
Posts: 786
Joined: Fri Jun 24, 2016 5:58 pm
Contact:

Re: Developing a RPG Engine for Renpy

#41 Post by kivik »

Did you declare your abilities with define or default? Or did you put it inside init or a label?

You have to make sure it's either default (outside of label blocks) or put it inside a label block after game starts, or it's not saved.

Long story short, if a variable (the variable name itself) is changed at label level (created counts as changed), then it's saved. If the variable doesn't change, it's not saved. One potential confusing thing is, you can create an instance of an object with variable a, and then change the attributes within the object you've created via a.attribute, that doesn't count as a change either AFAIK.

I think that's the issue here, but I'm not 100% sure without knowing how you declared everything.

User avatar
hyperionthunder
Regular
Posts: 51
Joined: Fri May 18, 2018 2:10 pm
Projects: The Wanderers
Deviantart: hyperionthunder
itch: hyperionthunder
Contact:

Re: Developing a RPG Engine for Renpy

#42 Post by hyperionthunder »

the abilities are defined within init python block

should i have the list defined like this?

Code: Select all

init -2 python:

    #define unchanged list of all abilities
    define ability_codes_list = {} #defines full dictionary to house ability codes
    define available_ability_codes = []
    define equipped_ability_codes = []

    #creates class AbilityCode to store data
    class AbilityCode:
        def __init__(self, name, desc, icon, unequipicon, mindamage, maxdamage, minheal, maxheal, cpcost, hpcost, element, elementicon, target, range, status, sprite, color, found, locked="locked_ac"):
            self.name = name
            self.desc = desc
            self.icon = icon
            self.unequipicon = unequipicon
            self.mindamage = mindamage
            self.maxdamage = maxdamage
            self.minheal = minheal
            self.maxheal = maxheal
            self.cpcost = cpcost
            self.hpcost = hpcost
            self.element = element
            self.elementicon = elementicon
            self.target = target
            self.range = range
            self.status = status
            self.sprite = sprite
            self.color = color
            self.found = found
            self.locked = locked

    #might move this into it's own .rpy file to house all of the available codes.
    # Name, Min Damage, Max Damage, Elemental, Target, All or Single , Active
    #fire set
    ability_codes_list[0] = AbilityCode(x, y, z)
    etc.
 

kivik
Miko-Class Veteran
Posts: 786
Joined: Fri Jun 24, 2016 5:58 pm
Contact:

Re: Developing a RPG Engine for Renpy

#43 Post by kivik »

define doesn't need to go inside init, although it works inside as well. With your list I think you should use default for available_ability_codes and equipped_ability_codes, outside of the init blocks. They'll get created immediately before your start label, and thus gets saved.

You COULD use default for your ability_codes_list as well, but you shouldn't need to - assuming that list shouldn't get changed mid game - and thus wouldn't require saving.

User avatar
hyperionthunder
Regular
Posts: 51
Joined: Fri May 18, 2018 2:10 pm
Projects: The Wanderers
Deviantart: hyperionthunder
itch: hyperionthunder
Contact:

Re: Developing a RPG Engine for Renpy

#44 Post by hyperionthunder »

since the party_list, reserve_party and equipped_ability_codes changes within the game, i went and write it in as default for these.

the available_ability_codes list is unchanging so i left it in the init block.

Post Reply

Who is online

Users browsing this forum: No registered users