Jump to content
  • Advertisement
  • entries
    18
  • comments
    12
  • views
    17428

About this blog

my journal while making a roguelike/RPG type game. I don't allways have much time to wrok on it, so hang in there. Also, I don't really expect anyone to read it.

Entries in this blog

 

Dungeon levels using threading

Phew, this was probably the most difficult feature to implement ever. Now it works though. A lot of you probably forgot about what project I was working on, so let me remind you: I am trying to make a isometric roguelike, with a lot of monsters and items and stuff. A game like this usually has a lot of levels, and finally, the same can be said for my game. Using some smart though buggy code, the level is generated in a seporate thread, then piped back to the main thread, partially parsed, and displayed. A level can usually be generated after only a few moves from the player, way before the player actually finds the exit.
While working, I kept getting extra strings and bytes stuck in the pipe. It turned out that the pipe was taking a really long time to start up, and the very first message took until much later in my other thread to actually come out, right in the middle of a check status loop. Luckilly, I found the solution in the middle of solving another bug.
A lot still needs to be done on this system though. Right now, only downwards motion is supported. The game generates the level immediately below your current level in advance, if the player somehow skips right on to another level, the game needs to start generating that level right away. I also need to store old levels before moving on to new levels. This way, if the player comes back up, he can get back his old stuff.

I am also probably going to decrease the size of each level dramatically. I hope to find a artist who can draw some higher resolution graphics, then display 10 times less pictures in 10 times higher quality. This way, the player won't have to spend a long time walking around the level, and can focus on interacting with stuff.

mousetail

mousetail

 

Multithreading!

Been a bit buizy with school, but found enough time to make a system where the level is generated in a separate process. The idea is that I can use that later to pregenerate the next level while still playing the previous one. Unfortunately, I can't do much loading while the level is being generated since I don't know what type of objects will be on the map. I might implement a system to load some common monster types ahead of time, and then unload unload and load any differences based on the actual level.
I am also kinda confused on what the best way to handle errors is. Right now the game keeps on running but does nothing if it has crashed since the level generation process is still running. A try... finally... that closes that process could work, but I don't know what would happen if that process throws a error.
The process itself was really easy using the "Multiprocessing" Library. It handles calling functions, sending complex objects, arguments, terminating, joining, and most of the stuff I thought I might have to do myself using subprocess. (That would have been a nightmare)
My todo list:
Propper progress bar on level generation, I broke my old console based system.
Loading the generated next level
Finding a way to customize the level generator for special rooms etc.

mousetail

mousetail

 

Philosophy on finishing projects

Let me start with the boring stuff: I implemented a system where items show the "Fake name" until they are identified using a identifier or by being used. The new name is only used in the inventory, since I now need a more complicated API to know the real name.
Now the interesting, philosophical part. At the beginning of this summer, I made a very complicated 2do list, with sub points, time estimates etc.

Right near the beginning, my 2do list was a subtask "Make and test a AI for using ranged combat." One purpose for ranged combat was so monsters could use it, so a AI that could use it, so this was a essential part of the major task "Ranged combat", so I went ahead and designed a type of monster that would shoot arrows, made a image for it, etc. Then I was about to code the monster, when I decided this was going to be easier to do after I had the XML loading in place, and then just add a attribute whether the monster should be able to throw stuff, what it should be able to throw, etc. The next major task on the list was item identification, but again I decided this would be easier to do after the XML loading also, so I went on to the next major task which was the XML loading itself. This one had a lot of subtasks, especially a lot of attributes to add for basic objects, like ways to define starting inventory, drops, etc. The list of stuff to do was growing faster then how fast I was plowing threw the work, and the work was very confusing, hardly visible, and I ended up procrastinating a lot. At the end, I decided that most of the attributes where non-essential and just move on to the next task, which was the very much related and a lot of the subtasks where more related to the other system really, like "A system to scramble the fake names so you don't know which item is which even if you replay the game" This would need to be implemented in the XML loading class, perhaps with a attribute added to a attr tag, something like "Scramble category", and then scramble the marked attributes later. This system would be important because I want the images to still match the name, even when the function doesn't. The complicatedness of this task was insane so I moved on to some other, later tasks (In this case, ability to limit a item or monster to a maximal amount of incarnations, that was incorrectly put under the item identification)

Now, however, my efforts are getting way to spread out. I am definitely not happy yet with the XML loading, so every other day I tell myself "Just finish that first" Then I look at the stuff to do, and decide I will jump to the next major task, which is very difficult to do without enough monsters registered, so I go back to another task, find it's do difficult also, and usually find a minor task I can do eventually. However, working on to many tasks at once feels bad for me (This is the philosophy part) since I end up working on none of them at all. All the tasks I skipped still really want me to do them before the later tasks, since I am afraid I will have the dirty work left after the summer, which is exactly the part I have the time to do now, but I also don't want old tasks to block my progress and motivation, where finishing new tasks first will make doing the old ones later easier, since even when they are unrelated I am avoiding writing code I will need to rewrite later. While I will probably decide which one to do in a couple of days, any advice on dealing with this situation is appreciated.

(And yes, the new game I downloaded has something to do with it)

mousetail

mousetail

 

XML loading working

I hate these types of improvements since you can't even see them at the surface, in fact, the game has become worse since I haven't moved all the old monsters to the new system yet. I have basic functions for parsing the parsed XML and passing the values to any constructor. This involves two functions, (One for monsters, one for items) that are called for each class, each returning a function passed to the loader. The loader calls this function, which returns another function, this function should return a instance of the object, that can be passed on to the world. The main difference between this architecture and the old one is that classes usually don't register themselves, as this creates a circular dependency, with the classes having to call a function on the XML-loader, while being lower down in the hierarchy. The new system has the XML-loader import a list of modules, each of which can define any class instancing functions.
The advantage of the new system is that a lot less data is stored in code. While I still have to register classes, the classes no longer have to contain any data, and many monsters can just share a class.
Way to much of my time was spent replacing calls to the old system with calls to the new system. The API is similar, but now you have to reach it threw the world object rather then the loader being a global variable.
While working on this, I found a point of inflexibility in the code: Weapons can't define special attacks. The variation in weapons is a list of damage types, but I want to be able to define really weird weapons that don't use the normal system. I think that if a monster has a special attack, like "claw", but is wielding a weapon, it shouldn't be able to use it's weaponless default. If I define a "default attack" for every weapon slot, and modify monster that way, it might be more dynamic. I am unsure however how I should handle multiple weapon slots. Most games seem to work by either picking a random one, or all at the same time. Since for humans the default would be punching the enemy, neither is very realistic. I am thinking something along the lines of "Try to hit with your sword, if you miss, try to hit with your fist, if you miss, try to bite, etc. depending on what attacks are available. I would like your ideas about this.
Thanks for reading!

mousetail

mousetail

 

Rearangements and utilites

So, I am trying to implement the XML. In the process, like usual, I got distracted by other stuff, like building a GUI for making combat maps. This small program (195 lines of code) allows you to draw rectangles on the gridded area, and then edit their attributes, like armor slot that protects it, or how vulnerable the square is to different types of damage, etc. When closing, it saves the data to a XML file, that can be copy-pasted into the monster definition XML file if desired, or just kept where it is, and the program loads it again next time it starts up. While more practical then inputting the values by hand as I have been doing, only by a little bit. The grid is 100 by 100, scaled up 7 times, making it hard to get in the place where you want to be, and making making almost symmetric people very difficult, since editing existing rectangles is not currently supported. What I end up doing is editing the save file by hand to correct small errors and round numbers etc. and then using to tool to see if it looks good. Entering the whole matrix of body part to damage type receptance is also still a pain, only improves slightly.
I also made a model XML file that my program should be able to load. Every entry consists of basic stats about when to spawn it (name, tags, best dungeon level, frequency, etc.) then a "Classdata" tag that contains attributes specific to what type of thing it is (Basic Item? Basic Monster? Container? Weapon?) For a monster, this would include the combat map that my other tool generated, for a weapon, it's the strength, throwing range, what tool will make it go farther etc. It's also possible to "inherit" a stat from another item, so all humanoids can inherit the combat map from the player object. You can inherit everything from another entry, or just one stat.
I programmed it to load the file, and check the inheritance, output looks like this:('CPP agent', (1, ('ITM_MONSTER', 'ITM_HUMANOID'), 'Human', {'speed': '120', 'attack_zones': {'rectangle': [{'slot': 'Head', 'damage': {'bio': '1', 'rad': '1', 'gas': '1', 'ele': '1', 'phis': '2', 'hea': '1', 'las': '2'}, 'dimentions': {'width': '18', 'top': '10', 'height': '16', 'left': '32'}}, {'slot': 'Body', 'damage': {'bio': '0', 'rad': '0', 'gas': '0', 'ele': '0', 'phis': '1', 'hea': '0', 'las': '0'}, 'dimentions': {'width': '18', 'top': '26', 'height': '17', 'left': '31'}}, {'slot': 'Body', 'damage': {'bio': '0', 'rad': '0', 'gas': '0', 'ele': '0', 'phis': '1', 'hea': '0', 'las': '0'}, 'dimentions': {'width': '6', 'top': '44', 'height': '7', 'left': '42'}}, {'slot': 'Body', 'damage': {'bio': '0', 'rad': '0', 'gas': '0', 'ele': '0', 'phis': '1', 'hea': '0', 'las': '0'}, 'dimentions': {'width': '18', 'top': '53', 'height': '7', 'left': '30'}}, {'slot': 'Legs', 'damage': {'bio': '0', 'rad': '0', 'gas': '0', 'ele': '0', 'phis': '1', 'hea': '0', 'las': '0'}, 'dimentions': {'width': '6', 'top': '62', 'height': '20', 'left': '42'}}, {'slot': 'Feet', 'damage': {'bio': '0', 'rad': '0', 'gas': '0', 'ele': '0', 'phis': '1', 'hea': '0', 'las': '0'}, 'dimentions': {'width': '10', 'top': '84', 'height': '4', 'left': '41'}}, {'slot': 'Feet', 'damage': {'bio': '0', 'rad': '0', 'gas': '0', 'ele': '0', 'phis': '1', 'hea': '0', 'las': '0'}, 'dimentions': {'width': '-10', 'top': '84', 'height': '5', 'left': '38'}}, {'slot': 'Legs', 'damage': {'bio': '0', 'rad': '0', 'gas': '0', 'ele': '0', 'phis': '1', 'hea': '0', 'las': '0'}, 'dimentions': {'width': '5', 'top': '62', 'height': '20', 'left': '32'}}, {'slot': 'Body', 'damage': {'bio': '0', 'rad': '0', 'gas': '0', 'ele': '0', 'phis': '1', 'hea': '0', 'las': '0'}, 'dimentions': {'width': '4', 'top': '28', 'height': '26', 'left': '52'}}, {'slot': 'Body', 'damage': {'bio': '0', 'rad': '0', 'gas': '0', 'ele': '0', 'phis': '1', 'hea': '0', 'las': '0'}, 'dimentions': {'width': '4', 'top': '32', 'height': '21', 'left': '24'}}, {'slot': 'Head', 'damage': {'bio': '0', 'rad': '0.25', 'gas': '0', 'ele': '2', 'phis': '0', 'hea': '0', 'las': '0'}, 'dimentions': {'width': '16', 'top': '4', 'height': '5', 'left': '34'}}, {'slot': 'Hands', 'damage': {'bio': '0', 'rad': '0', 'gas': '0', 'ele': '0', 'phis': '1', 'hea': '0', 'las': '0'}, 'dimentions': {'width': '4', 'top': '54', 'height': '3', 'left': '52'}}, {'slot': 'Hands', 'damage': {'bio': '0', 'rad': '0', 'gas': '0', 'ele': '0', 'phis': '1', 'hea': '0', 'las': '0'}, 'dimentions': {'width': '4', 'top': '54', 'height': '3', 'left': '24'}}]}, 'image_name': 'hero.png', 'advanced visibility check': 'True'}))('Goku', (4, (), 'Teleporters', {'speed': '120', 'attack_zones': {'rectangle': [{'slot': 'Head', 'damage': {'bio': '1', 'rad': '1', 'gas': '1', 'ele': '1', 'phis': '2', 'hea': '1', 'las': '2'}, 'dimentions': {'width': '18', 'top': '10', 'height': '16', 'left': '32'}}, {'slot': 'Body', 'damage': {'bio': '0', 'rad': '0', 'gas': '0', 'ele': '0', 'phis': '1', 'hea': '0', 'las': '0'}, 'dimentions': {'width': '18', 'top': '26', 'height': '17', 'left': '31'}}, {'slot': 'Body', 'damage': {'bio': '0', 'rad': '0', 'gas': '0', 'ele': '0', 'phis': '1', 'hea': '0', 'las': '0'}, 'dimentions': {'width': '6', 'top': '44', 'height': '7', 'left': '42'}}, {'slot': 'Body', 'damage': {'bio': '0', 'rad': '0', 'gas': '0', 'ele': '0', 'phis': '1', 'hea': '0', 'las': '0'}, 'dimentions': {'width': '18', 'top': '53', 'height': '7', 'left': '30'}}, {'slot': 'Legs', 'damage': {'bio': '0', 'rad': '0', 'gas': '0', 'ele': '0', 'phis': '1', 'hea': '0', 'las': '0'}, 'dimentions': {'width': '6', 'top': '62', 'height': '20', 'left': '42'}}, {'slot': 'Feet', 'damage': {'bio': '0', 'rad': '0', 'gas': '0', 'ele': '0', 'phis': '1', 'hea': '0', 'las': '0'}, 'dimentions': {'width': '10', 'top': '84', 'height': '4', 'left': '41'}}, {'slot': 'Feet', 'damage': {'bio': '0', 'rad': '0', 'gas': '0', 'ele': '0', 'phis': '1', 'hea': '0', 'las': '0'}, 'dimentions': {'width': '-10', 'top': '84', 'height': '5', 'left': '38'}}, {'slot': 'Legs', 'damage': {'bio': '0', 'rad': '0', 'gas': '0', 'ele': '0', 'phis': '1', 'hea': '0', 'las': '0'}, 'dimentions': {'width': '5', 'top': '62', 'height': '20', 'left': '32'}}, {'slot': 'Body', 'damage': {'bio': '0', 'rad': '0', 'gas': '0', 'ele': '0', 'phis': '1', 'hea': '0', 'las': '0'}, 'dimentions': {'width': '4', 'top': '28', 'height': '26', 'left': '52'}}, {'slot': 'Body', 'damage': {'bio': '0', 'rad': '0', 'gas': '0', 'ele': '0', 'phis': '1', 'hea': '0', 'las': '0'}, 'dimentions': {'width': '4', 'top': '32', 'height': '21', 'left': '24'}}, {'slot': 'Head', 'damage': {'bio': '0', 'rad': '0.25', 'gas': '0', 'ele': '2', 'phis': '0', 'hea': '0', 'las': '0'}, 'dimentions': {'width': '16', 'top': '4', 'height': '5', 'left': '34'}}, {'slot': 'Hands', 'damage': {'bio': '0', 'rad': '0', 'gas': '0', 'ele': '0', 'phis': '1', 'hea': '0', 'las': '0'}, 'dimentions': {'width': '4', 'top': '54', 'height': '3', 'left': '52'}}, {'slot': 'Hands', 'damage': {'bio': '0', 'rad': '0', 'gas': '0', 'ele': '0', 'phis': '1', 'hea': '0', 'las': '0'}, 'dimentions': {'width': '4', 'top': '54', 'height': '3', 'left': '24'}}]}, 'image_name': 'Goku.png', 'advanced visibility check': 'True'}))('human', (0, ('ITM_MONSTER', 'ITM_HUMANOID'), 'Human', {'attack_zones': {'rectangle': [{'slot': 'Head', 'damage': {'bio': '1', 'rad': '1', 'gas': '1', 'ele': '1', 'phis': '2', 'hea': '1', 'las': '2'}, 'dimentions': {'width': '18', 'top': '10', 'height': '16', 'left': '32'}}, {'slot': 'Body', 'damage': {'bio': '0', 'rad': '0', 'gas': '0', 'ele': '0', 'phis': '1', 'hea': '0', 'las': '0'}, 'dimentions': {'width': '18', 'top': '26', 'height': '17', 'left': '31'}}, {'slot': 'Body', 'damage': {'bio': '0', 'rad': '0', 'gas': '0', 'ele': '0', 'phis': '1', 'hea': '0', 'las': '0'}, 'dimentions': {'width': '6', 'top': '44', 'height': '7', 'left': '42'}}, {'slot': 'Body', 'damage': {'bio': '0', 'rad': '0', 'gas': '0', 'ele': '0', 'phis': '1', 'hea': '0', 'las': '0'}, 'dimentions': {'width': '18', 'top': '53', 'height': '7', 'left': '30'}}, {'slot': 'Legs', 'damage': {'bio': '0', 'rad': '0', 'gas': '0', 'ele': '0', 'phis': '1', 'hea': '0', 'las': '0'}, 'dimentions': {'width': '6', 'top': '62', 'height': '20', 'left': '42'}}, {'slot': 'Feet', 'damage': {'bio': '0', 'rad': '0', 'gas': '0', 'ele': '0', 'phis': '1', 'hea': '0', 'las': '0'}, 'dimentions': {'width': '10', 'top': '84', 'height': '4', 'left': '41'}}, {'slot': 'Feet', 'damage': {'bio': '0', 'rad': '0', 'gas': '0', 'ele': '0', 'phis': '1', 'hea': '0', 'las': '0'}, 'dimentions': {'width': '-10', 'top': '84', 'height': '5', 'left': '38'}}, {'slot': 'Legs', 'damage': {'bio': '0', 'rad': '0', 'gas': '0', 'ele': '0', 'phis': '1', 'hea': '0', 'las': '0'}, 'dimentions': {'width': '5', 'top': '62', 'height': '20', 'left': '32'}}, {'slot': 'Body', 'damage': {'bio': '0', 'rad': '0', 'gas': '0', 'ele': '0', 'phis': '1', 'hea': '0', 'las': '0'}, 'dimentions': {'width': '4', 'top': '28', 'height': '26', 'left': '52'}}, {'slot': 'Body', 'damage': {'bio': '0', 'rad': '0', 'gas': '0', 'ele': '0', 'phis': '1', 'hea': '0', 'las': '0'}, 'dimentions': {'width': '4', 'top': '32', 'height': '21', 'left': '24'}}, {'slot': 'Head', 'damage': {'bio': '0', 'rad': '0.25', 'gas': '0', 'ele': '2', 'phis': '0', 'hea': '0', 'las': '0'}, 'dimentions': {'width': '16', 'top': '4', 'height': '5', 'left': '34'}}, {'slot': 'Hands', 'damage': {'bio': '0', 'rad': '0', 'gas': '0', 'ele': '0', 'phis': '1', 'hea': '0', 'las': '0'}, 'dimentions': {'width': '4', 'top': '54', 'height': '3', 'left': '52'}}, {'slot': 'Hands', 'damage': {'bio': '0', 'rad': '0', 'gas': '0', 'ele': '0', 'phis': '1', 'hea': '0', 'las': '0'}, 'dimentions': {'width': '4', 'top': '54', 'height': '3', 'left': '24'}}]}, 'speed': '100', 'image_name': 'hero.png', 'advanced visibility check': 'True'}))
from this XML file: (I don't expect anybody to read this)
[spoiler]0humanITM_MONSTERITM_HUMANOID Human 100hero.png321018162121111Head312618171000000Body4244671000000Body30531871000000Body42626201000000Legs41841041000000Feet3884-1051000000Feet32625201000000Legs52284261000000Body24324211000000Body34416502000.2500Head5254431000000Hands2454431000000HandsTrueCPP agent1ITM_MONSTERITM_HUMANOID Human 120Goku4 TeleportersGoku.png
[/spoiler]
notice how Goku inherits from human, but overrides speed with CPP agent's speed, and image_name with it's own value. (Not that I have actually designed a type of monster named Goku, it's just a test). I will still need to do some complicated maneuvering to link each monster type with it's class, probably it will involve lots of if-else statements, with small functions in the XML loader class for every class.
Before I implement that, I am going to rearrange some of the modules to remove circular dependencies. Right now, I am using some weird hacks to get the program to run, like loading a module at the end of a code file instead of the beginning. However, I calculated that if I rearranged it enough, I could get nice, linear, dependencies, looking like this:

Right now, the item loader contains a couple classes that depend on items, and cheats depends on the item picker, but I think if I let cheats use utility functions in the world class and move the classes in the item loader to a different module, everything can be fixed and I am free to replace the item picker class to my new XML one.

Comments, suggestions, or any response is welcome!

mousetail

mousetail

 

Ranged potion throwing

Now, I am 2 days behind schedule, but throwing potions works mostly. The idea was that if you threw a potion at a monster, it would explode and apply the potion effect to all monsters inside the radius. Yesterday, I had it working that you could hit a monster using a potion, and the effect would be applied, today I have the explosion effect. I ran into a bug where it would fill the entire level with the explosion effect. It turned out I was editing the list of starting positions while I was adding more objects to it, causing the list to expand infinitively until it ran out of room.

Next up I want to redo the way monster types are stored, using separate XML files. Since I really want monsters to have unique attacks instead of a one-dimentional difficulty, a large percentage of monsters will be mirrored in a class in the code, with the XML containing metadata (spawn frequency, starting items, skills/stats, drops, etc. ) In the code, I will need to have a way for the XML to specify what class this object uses, resulting in still having a long list of functions to spawn different classes. I still hope that the new system will still be a improvement in usability, and it provides a easy entry point for plug ins that don't really need new code. If anybody has any suggestions or better ideas how to do this, you probably know more about this then I do.

This is my explosion image:

this effect contains 3600 particles!

Please comment if you have any suggestions or comments!

mousetail

mousetail

 

Ranged combat kinda working, and cheats

I was not looking forward to doing the re-structuralization required for making ranged combat work, so I made a cheat system first.
In the process, I found some bugs in the way the game decided when to redraw the screen. To be exact, the screen was redrawing every frame instead of only when changed. The problem was in the first write of the speed system, the players update returned a tuple: Should the screen redraw and should the monsters update. In the new system, world updates are handled completely separately, and objects update should return only a boolean whether to redraw the screen. In python, a tuple is treated like "True", so the screen redraws every frame. (Update function is means for things that need to happen every frame regardless of the rest of the world, so it can be used for switching animation frames or something)
I also in the process made some bugfixes in the console system. The textwarp tool I used removed spaces at the end of lines. In my cheat system, this took out all spaces since I put them at the end of the last line, then reformatted it into a new line, causing every space to be at the end of the line at some point, where it would be removed. I found a way to change this setting. I also added the feature to take out the last character before a \b allowing backspaces to be handled correctly.
The main thing I did, however, was the cheats system. Right now, you press capital C, then type in a cheat code, and stuff happens. I have two codes right now, and you'll have to guess what they do from the screen shot. (It's really obvious really). The complicated part was showing what the player was typing in the console, including the backspaces and stuff. I ran into a lot of bugs in the console system, like I outlined above.
Ranged combat is working kinda. I went with the model of having body inherit from a class with just the combat method. I can throw daggers, and kill stuff, however, the accuracy penalty means that its not really worthwhile until you reach like level 6 accuracy. The messages are also wrong, showing "You miss the lizard" instead of "The dagger misses the lizard" I also need to program effect of throwing other things e.g. potions.

Don't forget to check out my code at:
https://github.com/mousetail/Mousetail-s-roguelike

mousetail

mousetail

 

I'm stuck

So, I was working on the ranged combat system, and now you can throw items one spot by default, and I can set weapons to a custom range. I also found a couple of bugs in the system that only updated the display when it needed to, which made a nice animation when the dagger was flying. What is a bit annoying is the fact that items can move 8 squares in one turn, but it takes multiple turns to go farther. Though this is realistic and a cool mechanic, it feels unnatural to me, especially since the default range is exactly 8 squares.

But now why I am stuck. I implemented a system where a callback function would be called when a flying object hit something. Normally, I just stop the item and do nothing, but for weapons I need to apply damage. I looked at how the original attack function was coded, and found that monsters and players have a reference to a "body" object that handles combat and other type-specific functionality. Body has a function "Attack", that attacks a other body object. This means that I can override it in subclasses for more advanced attacks. For my projectile impact function, I wanted to do something similar to the basic attack function, (Not necessarily the attack function of the thrower) except modify some stats based on size. I tried just calling the method as a static method and passing a dummy object as self, but apparently self has to actually be a instance of the class. I came up with 3 ways to solve this:

A) Make body subclass a dummy class, except the dummy class has a fully functional attack method that I can use when I don't want to create a full instance of the body class

B) Just use a instance of body with a dummy reference to a monster object, so I can feed it it's own values

C) Make the dummy class inherit from monster body, and override getstat() so I can custom morph the accuracy stat

D) Make all attacks static functions in the main class, and have subclasses only specify which ones they know how to do. This way, I have removed almost all code from the monster classes and it solves a side problem because I can now define then in a CSV, XML or JSON file. However, I feel all those static methods will be extremely ugly.

E) Same as D, but define loose functions instead of static methods

F) Anybody have any more ideas?

(Oops, 5 options)

If anybody can give my any advice how to do this, it would be greatly appreciated!

(Ps. I uploaded it to github, and I will post a code guide when I have solved this issue)

mousetail

mousetail

 

Fixed fake bugs!

So, I spent the first hour of my time today debugging my combat system. I made a really nice overview of where I had last been hit where the monster was aiming for etc. However, it turned out to work perfectly so I wasted my time.

Next, I redid my speed potion. Modified my getspeed() function to take into account status messages, used by old but unused EventSceduler class to take out the status message at the right time, and redid my status messages system to use constants referring to ints instead of strings. However, this was waisted effort since the getspeed() function was never called, the world updater used player.speed directly, which recorded the base speed. I was wondering already why hunger induced slowness wasn't as significant as I expected. Fixed my world updater.

Next I thought that my speed potion wasn't working as expected. Spent half an hour debugging my speed system, then found it was some type of optical illusion, since all my "You miss the lizard" 's where either followed by or preceded by "The lizard misses you", but when the order changes, you don't notice that there are two about the same person in a row, since "You hit the lizard's left leg" and "You miss the lizard" have such a different length. It's a bit complicated to explain, but it was very convincing, and I am sure you would fall far it to, (or maybe not) and probably my players will also.

Next up either ranged combat, item identification, or maybe a few more monsters to test those out on.

Please don't hesitate to tell me what you think!

mousetail

mousetail

 

A awsome map of everything!

I got a new, very complete map of everything:

The weight system on the containers is working now and I have a different system to get random items, now based on tags, so if I want to fill a room with reptiles, I can only generate random monsters that are reptiles. If I want a chest of food, I can find random food.

mousetail

mousetail

 

Redid the speed system for the second (or was it third) time

So yea, I have a even better system for deciding who gets to move when. The last system was based on giving each object getting a amount of energy dependent on its speed, and then moving the maximum amount of times supported. Now I add a certain amount of energy to each object at the beginning of the loop, and then just keep looping till no object has enough energy to move, and then repeat. This approach is much simpler and faster then the last system, and it seems to work mostly. I also added support for costom actions like containers and other stuff.

The other thing I did was add containers. They work completely differently from how invetories work, so I might redo them sometime in the near future, to make nesting easier. Stacking is not yet working, and it needs stuff like capacity and weight calculations, however, I spent most of my time adding a framework for costom actions, like described above.

It's been such a long time since I have worked on this!

Any comments welcome!

mousetail

mousetail

 

New speed calculation system

As you can see on the picture, the most visible change is a way to make the log lines colored. You can choose from gray, white, red, yellow, and green right now. All you need to do is print "1}line" to get it red, but I am going to change it to "r}line" to make it easier to remember.
The title says I did a new speed calculation system, so I better go into that also. The old system used to be:
All monsters get updated
The player return True, meaning it did something
The game loops trough all the monsters
The world object divides the monsters speed by the players speed to determine how many moves it gets
The world calls AIturn that many times
AIturn adds event to the monsters event quire depending on AI calculations (For the player object, this list is filled with keyboard input)

The next round, when the monster updates, the base class (Player) will run threw the input and do stuff

The problem with that approach is that time is only counted for the amount of times its AI gets updated, but the player is being penalized for the amount of actions it takes. This mean monsters can take multiple actions per frame but the player can not. The AI should be based on a system where this doesn't happen, but never trust AI's. The AI coding was also more difficult because a lot of values where being checked that weren't important, like Event.Key when only Event.unicode was defined. You always had to check for everything
The new system works like this:
All monsters get updated
The player adds to its actions if it has a event
The world object loops thew all objects
if it has a action ready:
add object.speed to object.action points
if it has more then 0 action points:
subtract the value of all the pending actions from its action points
call process_actions()

else, if it doesn't, call AI turn, that adds a action. If the monster is a player, that dous nothing
Its actually a but more complicated, but it works. It is leggy though, since it takes a few frames for player input to get all the way threw the system. Here is the fuller code:[code=auto:62]while True: if self.objindex>=len(self.objects): self.objindex=0 obj=self.objects[self.objindex] if isinstance(obj, player_input.PlayerObject): if len(obj.actions)>0: obj.action_points+=obj.speed while obj.action_points>0: obj.action_points-=obj.gameTurn() redraw=True self.objindex+=1 else: obj.AIturn() break else: self.objindex+=1
Some other changes I made are: (I am using a lot of bullets today)
Count armor in weight calculation (before, it would be lighter to wear some stuff)
Make food more nutritious, so its actually possible to survive without starving
A few improvements to AI
reduce experience from eating food
better text wrapping system, making printing using commas possible
a few updated graphics (I didn't do this)

The next time I do something might be a few more month, so don't be surprised if you see nothing for some time. Be surprised if you see this in 2035 and it is still the last post.

mousetail

mousetail

 

Visibility Implemented! (and more stuff)

Now, I have a light system. You can't see a lot of the map unless you have explored. The system is pretty ugly and slow, since it just keeps a boolean grid that stores about every tile if it's visible or not. When you move, all tiles within a small radius are marked visable as long as nothing is blocking your view, and when you enter a room, the entire room is made visible. This makes the game feel a lot more dark already, but unlike other games of this type, this data is only one dimensional. What I mean is that in other games, if you have been somewhere, then you go somewhere else, and then a monster comes into the area you used to be, you can't see it, all you can see is the room like it was when you last saw it. The way my game is right now is if the grid says its visible, it will always be visible, and if you are close enough (limited by the size of your screen) you can see whatever is moving. I am going to think of a better way to do this.
Another thing you can see on the screen-shot is that I now have doors! You can open them, just by pressing O when you are next to them (no need to specify direction) and close them with C in the same way. They block your view, stop monsters, look cool and fill your log with "the door opens" times two hundred. Later, I am going to implement locked doors, but they are going to be handled a bit differently then other rogue-likes. I don't want doors to randomly be generated locked, I rather want that is the player finds a locked door, its probably a warning that there is something dangerous inside, or at least something special. That way, kicking doors won't be a useless waste of time, but give the player time to think if the actually wants to enter this dangerous room.
The third thing I did was make a weight system. Now, if you are carrying more then a certain amount of stuff (depending on your strength), you speed will drop by 25%. I don't actually have a way to train strength right now, and armor is not yet counted for weight.
I also implemented food. Its not very complicated right now, but if your hunger drops to much, you get slowed, and if its to high, you get slowed also. The lowest total speed you get from this is about 45%, meaning the average monster can do 2 moves+10% chance on one extra move for every one of your turns. Ill probably have to adjust these numbers later, but yea, it works.
I also made a few minor changes to the level generation code, to make passageways more windy. This introduces a few bugs, where doors are generated at corners, but those will be fixed soon.

My to do list for tomorrow:
Implement locked doors and special rooms
get rid of the green rooms for normal generation
a way to train strength
more work on the lighting system

To do later:
chests/containers
money/gems
shops
dungeon levels should be coming soon

This cool looking police guy doesn't want anybody entering the dungeon:

mousetail

mousetail

 

Monster spawning new and improved

So, I finally improved my list of monsters. Last time, I showed you I had everything in the __init__ of the world object, now I have a fancy system. All you need to do when you define a new monster, is make sure your arguments are the same as the default, and do it like this:[code=auto:9054]class whatever(object): @itemRandomizer.registerMonster(mindungeonlevel,maxdungeonlevel,propability,othersuff) def __init__(self, position, world):
and the monster will be generated. You can use the same system for items. The only problem is that itemRandomizer is the only instance of the class that defines the methods. Since all of this stuff is evaluated while the game is loading, I don't think it is a big problem. The class will also store data about whether unique items have been generated yet, when implemented, so the only reason I can see to have more then one is if you for some reason have 2 games going, like maybe hosting a server or something, but that seems really unlikely, and you can always deepcopy() it.

The other thing I did, though its related, is make monsters spawn even after the level has generated. This is proven, see the image, by me having 11 meat, while there are only 10 monsters generated when the level starts. The monsters are still really hard to find, since there are roughly 30 rooms in a 150*150 grid with a lot of space between them, so even with 10 on the level, you have to go around the grid a ton of times before you find one.

I also fixed a potential bug in the player input system that allowed you to do something without costing a move if you pressed a certain key the same frame. This also allowed me to update the screen without allowing monsters to move.

The other thing I did was implement armor. Armor reduces damage by multiplying it. So good armor would do something like 0.25 while horrible armor, like is available now, has 0.95, and the worst armor of all is more then one, so magnifying the damage done. I have different types of damage, so that could create the interesting situation where a piece of armor protects you from some extremely strong electrical attack, but actually increases the damage done by normal, physical, attacks. I also implemented that different monsters have different armor slots, or different amount of hands for multihanded fighting, though right now you can only fight with your right hand. (how do you like my pants image?)


Lizards are not a serious threat now if you have armor, level 2 accuracy, and a few potions of healing. This means I can start adding tougher monsters.

I also made some changes to the level generator. The generator is extremely buggy, so it tends to fill up whole areas with paths before finding the way to the next room. I had build a system that would detect if a path tile was surrounded by more path tiles, and remove it if it was. Only, it did it in some random situations, namely: (# is path)
####### ####### #
where it removed the central tile. Usually there was a way around, but it was a long way of, and potentially never, disconnecting the rooms.

Thanks for reading,
Me

mousetail

mousetail

 

Some random stuff

As you can see, there are some changes to the graphics, since my artist finished the first drawing. Spend half my time today changing the level generator so it would choose the right wall type, but when I finished, I realized I would need to do it again since a bit of floor is showing outside of the walls, because I was to lazy to have separate tiles for the bottom and top walls at the end of the room.

But for the things I achieved today. I mainly did a bunch of random stuff:
Swords now actually do something while wielded
You can take off armor and weapons
You can drink the potions, they heal you
Lizards now have a different map (its seen from the front, since I see no other way to fix that
Armor now has letters as well as a slot name (used to take it off)
Some utility functions: Remove from inventory by letter, remove from inventory by identity, remove wielded item by letter, gain level without losing your saved xp,
message when killed
support for different types of damage

This is all very random stuff, I don't feel like I achieved anything important today. Tomorrow, I will buid a armor system, as well as fixing the graphics related bug (there are actually more then you can see in screenshots). If I finish in time, I will make a better system for loading items, since right now its like:[code=auto:34] if i[1]=="player": pbody=player_input.monster_body.HumanBody tmpobjects.append(player_input.PlayerObject(i[0],pbody,self.cage,self)) self.focus=tmpobjects[-1] self.player=self.focus #These are usually equal, but sometimes the screen could focus on something else tmpobjects.append(items.HealingPotion(i[0],self.cage.lookup("potion.png"), self, self.cage,"potion")) elif i[1]=="lizzard": pbody=player_input.monster_body.lizard tmpobjects.append(player_input.MonsterObject(i[0],pbody,self.cage,self,75,name="lizard")) print tmpobjects[-1].position elif i[1]=="potion": tmpobjects.append(items.HealingPotion(i[0],self.cage.lookup("potion.png"), self, self.cage,"potion")) elif i[1]=="sword": tmpobjects.append(items.Weapon(i[0],self.cage.lookup("shortsword.png"),self, self.cage, "sword","swords",5,1)) else: print i
It works for the ammount of items I have now, but that is going to increase a lot, so its not going to be possible. Also, I need a way for monsters to be generated with certain Items in there inventory, witch will be easier if I make some utility functions that allow to create a item by name. Also, I will need to make a monster spawning system. That will take a few days. Only when I am perfectly happy with every system and the graphics am I going to add more levels and monsters, because even with only 2 types of monster (the player and the lizard) a lot of the time I need to make a change to all of them. (What is the radiation resistance of the lizard? same as human?)

mousetail

mousetail

 

Items system not really finished :-(

Well, I have been working on the item system today, but its pretty complicated. The trick is that every item can only be at one position at once. A lot of the time, when I dropped items, I either removed them from my inventory, found something was wrong, and lost my reference to it, and so it disappeared in nowhere. I imagine someone reaching the end level, picking up the , drop it in a safe place, and fount it has vanished into the void because you mistyped. I worked on that, and the next time, I dropped a item, it did appear on the ground, but it stayed in my inventory also. When I dropped it again, it moved to the new place, since I assigned the position when dropping. When all that was fixed, I tried to implement stacking. So I gave objects a __eq__ method, checking that the name was equal. But this turned into a problem, since I was using index() to find what item to pick up, but since they where equal, it just picked up the first item instead of the one you where trying. I try again by checking position as well, and setting positions in the inventory to [0,0]. Now, a new problem arises: The dropped items just carry a copy of the players position list, so when the players position changes, the items position changes with it. Changed the position to a tuple. Now, everything seems to work, only, the widget seems to only change when I press a key instead of when I click on it. Turns out a key triggers the screen to update while a mouse doesn't. Fixed that. By now, its late in the afternoon and I didn't think I would be able to get a wielding system done in time.(Killed by a lizard, right now lizard are basically equally strong as you)
Anyway, at like 6, I started on a wielding system. Found a ton of bugs in the drop system in the process (more duplicate items). The logic was basically the same for the rest. I decided to have one w which would wear/wield anything. For nethack style creative wielding, you would use capital W. As you can see in the screen shot (the first one) I have a sword equipped in my right hand and also in my left. You can also see what slots are free. My idea is that different monsters would have entirely different slots, to make sure giants don't wear gnome armor, like you can do in most other games. Swords don't actually affect combat yet, but who cares, they look cool.
I need a idea for a better way to handle longer and flatter monsters in my combat system. Right now combat works by stabbing from the front in random places, but for monsters that have more surface area on the top, it doesn't really work. Anybody have any ideas?

mousetail

mousetail

 

combat system finished!

As you can see in the screen shot, (or if its to small, as you can't see) the top of the screen is cleared now and displays stats. The right side shows what is happening to your combat. As you can see, when the accuracy increased, (the mountain message) you were suddenly a lot more likely to hit where you where aiming. I haven't really explained how this aiming system works properly, so let me explain it again:
First, the monster (or player) decides on a target. The target is where it is trying to aim. This can be any square in the image I showed last time. Default is ribs (torso) and there is no way to change that yet. Off cource, some monsters will have different body parts, and scales between them, as well as weakness, so you need to count that. The target additionally needs to be in reach of shorter monsters.
Next the distance is calculated. The basic formula is random(0,1)[sup]2[/sup]x100/accuracy. The direction is simply a random number between 0 and 2 pi. I use sine and cosine to find the point, and check what zone is in there.
Damage is calculated using random(1,2) X level X weakness of the point. Armor and weapons will go into this calculation when I have a Item system.

This system is pretty complicated for this type of game, but I think it can add a lot of fun to gameplay if you are a advanced player, and it dousn't really affect you if you are a newbie, since the default settings should be good.
I also improved the AI of monsters to, at least, attack you when you are right next to them. Since they can't tell the difference between a player and each other, they will attack each other to if they happen to get close enough (witch never seems to happen) They are a very significant treat, since HP regeneration is very slow, and right now I usually die killing my second or third lizard. They will get easier if I implement weapons, as well as there size. (Right now they are considered humans with AI)
Next up is a inventory system, witch I will need to implement weapon and armor. As you can see in the screen shot, I have drawn a simple potion witch will be my test object. Right now you can pick it up, witch places it in a list thats called inventory, and nothing more can be done. You can't even see that its there. If I finish the inventory system, I will work on a armor system, as well as rescaling the lizards.

mousetail

mousetail

 

Working on combat system

So, I was feeling a bit less motivated, so I decided to keep a blog again! Unfortunately, this meant I had to delete my old blog, but luckily I could keep the points I got from there.
But, this blog is supposed to be about my project. So a while ago, in august 2014, I decided I had done enough simple and medium programs, and it was time to move into something more difficult, like a RPG. So I worked in C# on that for a while, but I got stuck and had to start over. This happened several times until one day I was a bit sick, and I was randomly trying to code a level generator just to discover how they work, when I suddenly thought of why I couldn't just use procedurally generated levels in a RPG. I had been playing a lot of NetHack (I'm old fashioned, I don't care) and I could see a lot of RPG elements in there, like levels and stuff, so I thought I would just make my RPG more like a rogue like (whats the difference anyway). I continued to work on that one since then, and I am pretty confident I will be able to finish this eventually. The language I was playing around in was python, so I guess I am stuck with that now.
OK, enough for the dreaming, lets talk about what I have got. I got a nice picture that shows my class hierarchy:

So, main is in charge of getting a display window, and getting instances of all the other classes, passing events on to the world object, and telling windows when they should rerender themselves. The world object stores data about the current level and passes events from the main on to the player object, tells the AI of monsters when they are allowed to update based on there speed (I don't actually have a real AI implemented yet, they just choose a random direction to move in.
I have 3 window objects: One to show the screen, one to show the messages, and one to show the stats. The last one is just a class: pass block, right now, so that is why the top of the screen isn't cleared since the loading screen.
The grid object takes care of generating the level, and stores the base level (Not objects, like the player, only walls and floors)
The Player/Monster object stores stats of the object, and has AI code if its a monster. (Monsters inherit from player, they override the receive event function)
A body object is attached to a player/monster object, but changes if it would polymorph. It defines stuff like special combat attacks, tile visibility, stat ratio and what items would be worn. It hardly stores any data, this is all classlevel. Note that the way I handle XP earned while polymorhed is add it normally to your XP, but the monsters ratio will determine how much the monster can use.
The combat system is based on several targets on the monster, wich have different damage ratios, They have a posiiton and size, wich determines how likely you are to miss, and what other body parts you are likely to hit if you miss. Here is the map for human:

its upside down because I am measuring distance from the ground.
The idea is also that shorter monsters can't hit higher up body parts. This last part about combat isn't actually implemented yet, but I will probably finish that tomorrow, I will probably post again tomorrow.
Thanks for reading!
(I don't actually expect anyone to read this)

mousetail

mousetail

  • Advertisement
×

Important Information

By using GameDev.net, you agree to our community Guidelines, Terms of Use, and Privacy Policy.

GameDev.net is your game development community. Create an account for your GameDev Portfolio and participate in the largest developer community in the games industry.

Sign me up!