Permalink
Browse files

initial commit

  • Loading branch information...
0 parents commit c5e45692a098ebd2d31be7e376f209b371cec204 @bitcraft committed Mar 24, 2012
Showing with 30,618 additions and 0 deletions.
  1. +25 −0 README
  2. +207 −0 buildworld.py
  3. +7 −0 cleanup.sh
  4. +48 −0 docs/CREDITS
  5. +36 −0 docs/NOTES
  6. +40 −0 docs/PYWEEK
  7. +163 −0 docs/SCRIPT
  8. +48 −0 docs/TODO
  9. BIN lib/.DS_Store
  10. +1 −0 lib/__init__.py
  11. +361 −0 lib/battlestate.py
  12. +14 −0 lib/conditions.py
  13. +159 −0 lib/cutscene.py
  14. +239 −0 lib/dialog.py
  15. +12 −0 lib/gui.py
  16. +277 −0 lib/mdna.py
  17. +31 −0 lib/misc.py
  18. +435 −0 lib/mob.py
  19. +170 −0 lib/overworld.py
  20. +41 −0 lib/pausestate.py
  21. +135 −0 lib/renderer.py
  22. +250 −0 lib/rpg.py
  23. +142 −0 lib/spells.py
  24. +175 −0 lib/titlescreen.py
  25. +399 −0 lib/worldstate.py
  26. +1 −0 lib2d/__init__.py
  27. +121 −0 lib2d/ani.py
  28. +426 −0 lib2d/avatar.py
  29. +189 −0 lib2d/banner.py
  30. +247 −0 lib2d/bbox.py
  31. +57 −0 lib2d/buttons.py
  32. +310 −0 lib2d/cmenu.py
  33. +38 −0 lib2d/config.py
  34. +2,468 −0 lib2d/configobj.py
  35. +166 −0 lib2d/cursor.py
  36. +415 −0 lib2d/env.py
  37. +119 −0 lib2d/fov.py
  38. +375 −0 lib2d/fsa.py
  39. +31 −0 lib2d/game.py
  40. +121 −0 lib2d/gamestate.py
  41. +113 −0 lib2d/gfx.py
  42. +236 −0 lib2d/gui.py
  43. +154 −0 lib2d/los.py
  44. +90 −0 lib2d/maputils.py
  45. +312 −0 lib2d/objects.py
  46. +380 −0 lib2d/oldtilemap.py
  47. +191 −0 lib2d/playerinput.py
  48. +301 −0 lib2d/quadtree.py
  49. +252 −0 lib2d/rect.py
  50. +148 −0 lib2d/res.py
  51. +296 −0 lib2d/statedriver.py
  52. +120 −0 lib2d/subpixelsurface.py
  53. +454 −0 lib2d/tilemap.py
  54. +64 −0 lib2d/timer.py
  55. +964 −0 lib2d/tmxloader.py
  56. +607 −0 lib2d/tmxloader3.py
  57. +332 −0 lib2d/vec.py
  58. BIN mh-data.txt
  59. BIN mh-index.txt
  60. BIN resources/fonts/04b.ttf
  61. BIN resources/fonts/about.gif
  62. BIN resources/fonts/dpcomic.ttf
  63. +17 −0 resources/fonts/dpcomic.txt
  64. +4 −0 resources/fonts/license.txt
  65. BIN resources/fonts/northwoodhigh.ttf
  66. +16 −0 resources/fonts/northwoodhigh.txt
  67. BIN resources/fonts/pixilator.ttf
  68. +26 −0 resources/fonts/read me.txt
  69. BIN resources/fonts/red_mamba.ttf
  70. +86 −0 resources/fonts/visitor.txt
  71. BIN resources/fonts/visitor1.ttf
  72. BIN resources/fonts/volter.ttf
  73. BIN resources/images/.DS_Store
  74. BIN resources/images/16x16-forest-town.png
  75. BIN resources/images/2_fog_dwell_peaks.png
  76. BIN resources/images/beast.png
  77. BIN resources/images/cave_bluelarge.png
  78. BIN resources/images/dialog2-h.png
  79. BIN resources/images/dialog2.png
  80. BIN resources/images/dialogs.png
  81. BIN resources/images/grads.png
  82. BIN resources/images/grasp.png
  83. BIN resources/images/healer-female-walk.png
  84. BIN resources/images/healer-male-walk.png
  85. BIN resources/images/magician-female-walk.png
  86. BIN resources/images/magician-male-walk.png
  87. BIN resources/images/map-fade.png
  88. BIN resources/images/next_arrow.png
  89. BIN resources/images/ninja-female-walk.png
  90. BIN resources/images/ninja-male-walk.png
  91. BIN resources/images/open.png
  92. BIN resources/images/overpass.png
  93. BIN resources/images/overworld.png
  94. BIN resources/images/portrait0.png
  95. BIN resources/images/portrait1.png
  96. BIN resources/images/ranger-female-walk.png
  97. BIN resources/images/ranger-male-walk.png
  98. BIN resources/images/spellicons.png
  99. BIN resources/images/townfolk-female-walk.png
  100. BIN resources/images/townfolk-male-walk.png
  101. BIN resources/images/wait_arrow.png
  102. BIN resources/images/warrior-female-walk.png
  103. BIN resources/images/warrior-male-attack.png
  104. BIN resources/images/warrior-male-stand.png
  105. BIN resources/images/warrior-male-walk.png
  106. BIN resources/images/wmap-fade.png
  107. +37 −0 resources/maps/bigmap.tmx
  108. +42 −0 resources/maps/building0.tmx
  109. +29 −0 resources/maps/building1.tmx
  110. +29 −0 resources/maps/building2.tmx
  111. +29 −0 resources/maps/building3.tmx
  112. +29 −0 resources/maps/building4.tmx
  113. +29 −0 resources/maps/building5.tmx
  114. +29 −0 resources/maps/building6.tmx
  115. +29 −0 resources/maps/building7.tmx
  116. BIN resources/maps/controlset.png
  117. +29 −0 resources/maps/hall.tmx
  118. +34 −0 resources/maps/multi-concept.tmx
  119. +37 −0 resources/maps/overworld-base.tmx
  120. +24 −0 resources/maps/overworld-simple.tmx
  121. +117 −0 resources/maps/overworld-test.tmx
  122. +282 −0 resources/maps/overworld.tmx
  123. +26 −0 resources/maps/overworld2.tmx
  124. +26 −0 resources/maps/overworld3.tmx
  125. +1 −0 resources/maps/rules.txt
  126. +117 −0 resources/maps/rules/overworld16.tmx
  127. +42 −0 resources/maps/testworld.tmx
  128. +63 −0 resources/maps/village.tmx
  129. BIN resources/music/relent.ogg
  130. +31 −0 resources/scripts/intro.txt
  131. BIN resources/sounds/.DS_Store
  132. BIN resources/sounds/bump0.wav
  133. BIN resources/sounds/grass0.wav
  134. BIN resources/sounds/select0.wav
  135. BIN resources/sounds/select1.wav
  136. BIN resources/sounds/stone0.wav
  137. BIN resources/sounds/wood0.wav
  138. BIN resources/tilesets/10_basic_message_boxes_by_CharlesGabriel.png
  139. BIN resources/tilesets/16x16-forest-town.png
  140. BIN resources/tilesets/16x16-indoor.png
  141. BIN resources/tilesets/16x16-overworld.png
  142. BIN resources/tilesets/16x18-2_0.png
  143. BIN resources/tilesets/OGA_16x18 zombie charas complete.png
  144. BIN resources/tilesets/charsets_12_characters_4thsheet_completed_by_antifarea.png
  145. BIN resources/tilesets/controlset.png
  146. +12,548 −0 resources/tilesets/controlset.tsx
  147. BIN resources/tilesets/mechanics.png
  148. BIN resources/tilesets/rpg indoor tileset expansion 1 trans.png
  149. BIN resources/tilesets/rpgsprites1/healer_f.png
  150. BIN resources/tilesets/rpgsprites1/healer_m.png
  151. BIN resources/tilesets/rpgsprites1/mage_f.png
  152. BIN resources/tilesets/rpgsprites1/mage_m.png
  153. BIN resources/tilesets/rpgsprites1/ninja_f.png
  154. BIN resources/tilesets/rpgsprites1/ninja_m.png
  155. BIN resources/tilesets/rpgsprites1/ranger_f.png
  156. BIN resources/tilesets/rpgsprites1/ranger_m.png
  157. BIN resources/tilesets/rpgsprites1/townfolk1_f.png
  158. BIN resources/tilesets/rpgsprites1/townfolk1_m.png
  159. BIN resources/tilesets/rpgsprites1/warrior_f.png
  160. BIN resources/tilesets/rpgsprites1/warrior_m.png
  161. +88 −0 run.py
  162. BIN utilities/.DS_Store
  163. +146 −0 utilities/benchmarks.py
  164. +69 −0 utilities/chop.py
  165. +2,468 −0 utilities/configobj.py
  166. +174 −0 utilities/cutup.py
  167. +172 −0 utilities/mk_tileset.py
  168. +43 −0 utilities/shadows.py
  169. +137 −0 utilities/slice.py
25 README
@@ -0,0 +1,25 @@
+this is a ongoing project for some sort of adventure game.
+
+python/pygame adventure game library
+ native support for tiled maps
+ supports keyboard and joystick movement
+ save game and persistence for all objects
+ scrolling map
+ animated sprites
+
+
+started during pyweek sept '11, has slowly morphed into something else.
+
+
+people interested in the game are more than welcome to fork the game and try
+to make something of it. i develop the game and library, lib2d, in my spare
+time and sometimes will make huge changes. that being said, there isn't much
+documentation except for the comments.
+
+the game uses a pickle to save the game. if you make changes to any class that
+exists in the pickle (area, avatarobject, ...), then you will have to rebuild
+the game pickle. just run "buildworld.py". this is also the script that
+generates the starting world.
+
+the guid's set in the pickle match the guid's stored in control.tsx found in
+tilesets.
207 buildworld.py
@@ -0,0 +1,207 @@
+"""
+Game world for "mh"
+
+this will create a pickle that can be read by the library
+"""
+
+from lib2d.env import Environment, Area
+from lib2d.avatar import Avatar, Animation, StaticAnimation
+from lib2d.objects import AvatarObject
+from lib2d import res, tmxloader
+from lib.rpg import Hero, NPC
+
+from collections import defaultdict
+
+
+
+# build the initial environment
+uni = Environment()
+uni.name = 'MH'
+uni.setGUID(0)
+
+# build our avatars and heros
+avatar = Avatar()
+ani = Animation("warrior-male-stand.png", "stand", 1, 4)
+avatar.add(ani)
+ani = Animation("warrior-male-walk.png", "walk", [0,1,2,1], 4)
+avatar.add(ani)
+ani = Animation("warrior-male-attack.png", "attack", 4, 4, 60)
+avatar.add(ani)
+avatar.play("walk")
+npc = Hero()
+npc.setName("Rat")
+npc.setAvatar(avatar)
+npc.setGUID(1)
+uni.add(npc)
+
+
+avatar = Avatar()
+ani = Animation("townfolk-male-walk.png", "walk", [0,1,2,1], 4)
+avatar.add(ani)
+npc = NPC()
+npc.setName("Mayor")
+npc.setAvatar(avatar)
+npc.setGUID(2)
+uni.add(npc)
+
+
+avatar = Avatar()
+ani = Animation("ranger-male-walk.png", "walk", [0,1,2,1], 4)
+avatar.add(ani)
+npc = NPC()
+npc.setName("Bolt")
+npc.setAvatar(avatar)
+npc.setGUID(3)
+uni.add(npc)
+
+
+avatar = Avatar()
+ani = Animation("healer-male-walk.png", "walk", [0,1,2,1], 4)
+avatar.add(ani)
+npc = NPC()
+npc.setName("Ax")
+npc.setAvatar(avatar)
+npc.setGUID(4)
+uni.add(npc)
+
+
+avatar = Avatar()
+ani = Animation("healer-female-walk.png", "walk", [0,1,2,1], 4)
+avatar.add(ani)
+npc = NPC()
+npc.setName("Tooth")
+npc.setAvatar(avatar)
+npc.setGUID(5)
+uni.add(npc)
+
+
+avatar = Avatar()
+ani = Animation("magician-male-walk.png", "walk", [0,1,2,1], 4)
+avatar.add(ani)
+npc = NPC()
+npc.setName("Nail")
+npc.setAvatar(avatar)
+npc.setGUID(6)
+uni.add(npc)
+
+
+avatar = Avatar()
+ani = StaticAnimation("16x16-forest-town.png", "barrel", (9,1), (16,16))
+avatar.add(ani)
+item = AvatarObject()
+item.pushable = True
+item.setName("Barrel")
+item.setAvatar(avatar)
+item.setGUID(513)
+uni.add(item)
+
+avatar = Avatar()
+ani = StaticAnimation("16x16-forest-town.png", "sign", (11,1), (16,16))
+avatar.add(ani)
+item = AvatarObject()
+item.pushable = False
+item.setName("Sign")
+item.setAvatar(avatar)
+item.setGUID(514)
+uni.add(item)
+
+avatar = Avatar()
+ani = StaticAnimation("16x16-forest-town.png", "rock", (8,9), (16,16))
+avatar.add(ani)
+item = AvatarObject()
+item.pushable = True
+item.setName("Rock")
+item.setAvatar(avatar)
+item.setGUID(515)
+uni.add(item)
+
+avatar = Avatar()
+ani = StaticAnimation("16x16-forest-town.png", "stump", (8,7), (16,16))
+avatar.add(ani)
+item = AvatarObject()
+item.pushable = False
+item.setName("Stump")
+item.setAvatar(avatar)
+item.setGUID(516)
+uni.add(item)
+
+avatar = Avatar()
+ani = StaticAnimation("16x16-forest-town.png", "stump", (8,7), (16,16))
+avatar.add(ani)
+item = AvatarObject()
+item.pushable = False
+item.setName("Stump")
+item.setAvatar(avatar)
+item.setGUID(517)
+uni.add(item)
+
+avatar = Avatar()
+ani = StaticAnimation("16x16-forest-town.png", "stump", (8,7), (16,16))
+avatar.add(ani)
+item = AvatarObject()
+item.pushable = False
+item.setName("Stump")
+item.setAvatar(avatar)
+item.setGUID(518)
+uni.add(item)
+
+avatar = Avatar()
+ani = StaticAnimation("16x16-forest-town.png", "stump", (8,7), (16,16))
+avatar.add(ani)
+item = AvatarObject()
+item.pushable = False
+item.setName("Stump")
+item.setAvatar(avatar)
+item.setGUID(519)
+uni.add(item)
+
+avatar = Avatar()
+ani = StaticAnimation("16x16-forest-town.png", "stump", (8,7), (16,16))
+avatar.add(ani)
+item = AvatarObject()
+item.pushable = False
+item.setName("Stump")
+item.setAvatar(avatar)
+item.setGUID(520)
+uni.add(item)
+
+
+
+# build the areas to explore
+
+village = Area()
+uni.add(village)
+village.setMap("village.tmx")
+village.setName("Village")
+village.setGUID(1001)
+
+
+home = Area()
+uni.add(home)
+home.setMap("building0.tmx")
+home.setName("Building0")
+home.setGUID(1002)
+
+
+# finialize exits by adding the needed references
+
+allAreas = [ i for i in uni.getChildren() if isinstance(i, Area) ]
+allExits = defaultdict(list)
+
+# make table of all exits
+for area in allAreas:
+ for guid in area.exits.keys():
+ allExits[guid].append(area)
+ print guid, area
+
+# set the exits properly
+for guid, areaList in allExits.items():
+ if len(areaList) == 2:
+ areaList[0].exits[guid] = (areaList[0].exits[guid][0], areaList[1].guid)
+ areaList[1].exits[guid] = (areaList[1].exits[guid][0], areaList[0].guid)
+
+ print areaList[0], areaList[0].exits
+ print areaList[1], areaList[1].exits
+
+
+uni.save("mh")
7 cleanup.sh
@@ -0,0 +1,7 @@
+find . -name \.DS_Store -type f -delete
+find . -name \*~ -type f -delete
+find . -name \*pyc -type f -delete
+find . -name \*.swp -type f -delete
+find . -type d -print0 | xargs -0 chmod 755
+find . -type f -print0 | xargs -0 chmod 644
+find . -type f -print0 | xargs -0 touch -ma
48 docs/CREDITS
@@ -0,0 +1,48 @@
+credits for game assets:
+
+fonts
+see font directory for information
+
+700+ RPG Icons Lorc
+http://opengameart.org/content/700-rpg-icons
+
+10 Basic Message Boxes Charles Gabriel
+http://opengameart.org/content/10-basic-message-boxes
+
+RPG Indoor Tileset: Expansion 1 Redshrike
+http://opengameart.org/content/rpg-indoor-tileset-expansion-1
+
+16x16 indoor rpg tileset: the baseline Redshrike
+http://opengameart.org/content/16x16-indoor-rpg-tileset-the-baseline
+
+portraits justin nichol, clint bellanger
+http://opengameart.org/content/flare-portrait-pack-number-one
+
+Beast 001 Concept misha
+http://opengameart.org/content/beast-001-concept
+
+Cave pictures/icons Blarumyrran
+http://opengameart.org/content/cave-picturesicons
+
+Backgrounds Écrivain
+http://opengameart.org/content/backgrounds-0
+
+found on opengameart.org:
+16x16 town & forest tiles surt
+16x18 characters antifarea
+16x16 overworld tiles MrBeast
+
+
+many sounds were edited with audacity.
+sounds were downloaded from freesounds.org with authors listed.
+wood0.wav: "wood3.wav", wildweasel
+stone0.wav: "gravel walking.aif", tigersound
+grass0.wav: "WalkMedDryGrass.wav", UATaudio
+all others were created by me with bfsxr
+
+relent.ogg kevin macleod
+http://incompetech.com/m/c/royalty-free/index.html?keywords=relent
+
+if you have any questions, or you notice the information is incorrect, please contect me.
+
+leif.theden@gmail.com
36 docs/NOTES
@@ -0,0 +1,36 @@
+# resize the "700+ icon set to usable sizes"
+
+# remove the border of each image
+mogrify -shave 14x14
+
+# make the tilemap, thing...
+montage -geometry 64x64 -tile 25x32 *.png iconset.png
+
+
+
+
+questing:
+
+a quest:
+ has a goal
+ has prerequsits to finish (quests too?)
+ has a defined consequence when completed
+ has a defined consequence while being completed
+
+
+any game item:
+ has a defined state when certain quest conditions are met
+ can belong to "state groups" to simplify conditions that affect many objects
+
+
+karmatic and reputation system
+ completed quests will raise and lower reputation for factions
+ karma can be raised and lowered through actions
+ loyality or opposition will affect dialog choices
+
+
+dialog choices
+ dialog choices will be available and subject to different factors
+
+
+
40 docs/PYWEEK
@@ -0,0 +1,40 @@
+Pyweek #2: done!
+
+It was a whirlwind of coding, although not much of a game came out of it. As I was developing the game, I kept a journal of ideas to add. Over the week, I just had a ton on inspiration, but I think that the idea was not really a good fit for a week deadline.
+
+Things I did right:
+Good games come from good ideas, so I spent the first day brainstorming and developing ideas (i also didn't have time to code!)
+Breaking down elements of the game into manageable chunks, setting goals for each (i.e.: next hour get the input working)
+Using an established codebase (lib2d from my last pyweek)
+Research into genetic algorithms helped a lot
+Using Tiled for map creation
+
+Things that I didn't do right:
+Being waaaay too ambitious
+Getting stuck on details
+Not spending time on art, sounds, music
+Literally in the last hours, modifying the map rendering system!
+
+The concept that I had in mind in bullet points:
+* the core mechanic would be that monsters could breed with each other
+* you could capture them as well, selectively breed them for qualities that you liked use them as pets
+* you could release your bred creatures into wild populations get the other monitors to interbreed with it
+* the populations would have home areas, and all have similar traits
+
+Maybe, one group is tough to kill because they run quickly. so you release slow moving creatures in that area, and maybe in a couple generations they will be easier to kill/catch.
+
+Since the end of this pyweek, I've worked out a few bugs and maybe in a couple weeks, I'll have more to show for the game. I really like the concept and I'm going to continue tinkering with it for a while.
+
+A few people have talked to me about Lib2d, which I'm going to package separately and document.
+The most important features of Lib2d that helped the pyweek were:
+Simple ini fie based animations
+Fast multi-layer tilemap renderer
+Solid state-management that [imo] simplifies and consolidates input, displays, and game flow
+"Avatar" class for animated sprites with multiple directions
+I also used DRI0D's tmx loader, but I've since abandoned it since I couldn't really figure out how to use it with the latest version.
+
+
+There is a repackaged version of the game (i'll just call it MH) with the renderer fixed, a new loader, and a draft of the script that I wrote for the story.
+
+
+Now, who wants to draw me some sprites? ^_^
163 docs/SCRIPT
@@ -0,0 +1,163 @@
+Action RPG story.
+
+script #1
+
+the character's name is Rat.
+
+main character, wandering mercenary.  doesn't have dialog, like link.  while link has a positive attitude, the others are more or less indifferent toward the character.
+
+the town has been overrun by monsters and nobody knows where they have come from.  your character has a past with them, but will not lead onto others how or why he understands them.  he comes to the town and establishes himself.  he has been summoned by the leader of the village to come and rid the town of monster.
+
+the story takes  place over two years with 4 seasons.  time is compressed like harvest moon.  day and night cycles are available.
+
+rat comes in and finds a few suitable people to assist him.  dealing with the creatures not only requires strength, but also careful planning and science.  rat finds 2 young people who study manuals that rat has brought with him and learn what the creatures are and how they can defeat them.
+
+the creatures, they find, are not from this planet and used to live on a planet with much faster cycles of day and night.  they have adapted to rapid reproduction and extreme mutations.
+
+one of his helpers realizes that rat is actually a hunter from where the creatures are from and follows them, determined to exterminate them all.
+
+script #2
+
+rat enters the town with a small group of other bounty hunters to the town.  a few months ago a mine was opened in the town that released the creatures (“disorder”).  the other hunters discuss their opinions on why the disorder exists, where they came from ect.  rat remains silent.
+
+once they reach the town, they are greeted by the mayor of the town and are escorted to an inn where they will be staying.  they hear that another hunter has already reached the inn, but has been in the hunt for a few days.
+
+they have arrived during the day.  night time is not safe.
+
+it has been a few months since the disorder escaped from the caves and dispersed.  little is known about them.  indeed, when the cave was opened, nothing incredible happened that day, but during the night terrible noises were heard from the cave.  over the next few days the sounds became more distant, but seemed to encircle the town.
+
+the town, set in a depression, is a bowl shape and so far the disorder has not ventured beyond the mountains.
+
+the first hunter was a local townsman, ax.  he reasoned that since they waited until night to escape the cave, that they must not like the the sun.  this has been verified since they have not been seen by locals during the day, but they cries can be heard at night.
+
+the first day, rat hears other stories.
+
+livestock have been missing.  after a week or so, the sounds would get near the town, but never too close.  once ax realized that the disorder will not approach light, the townspeople felled trees and created large torches around the perimeter of the town.  each night 2 groups of three men were assigned to watch the torches and keep the disorder out from the town.  this has worked for months now and while the sounds can still be heard, the livestock is protected and they have not approached the town.
+
+a few weeks ago, ax and the blacksmith worked together to create a portable torch that could be used to provide light bright enough to scare the disorder.  their first lantern was much larger than a most lanterns, but required special materials to create the oil, lens, and frame.  ax used it when he would go solo at night to find the disorder.
+
+ax is said to have seen several of the beasts.  some people were skeptical about his stories since his descriptions of the disorder seemed so exerated.  it is ax who named the beasts 'disorder' based on what he found in his few trips.  he claimed that each creature is almost totally unique, unlike the beasts that are familiar to man.  where each beast known to man will be made in the same mold as its parents, it is not true with these creatures.  each one seems totally unique, even if found together.
+
+his first encounter, ax found 4 disorders together around the corpse of a cow.  he described them as pale, about as large as a child.  each one had a similar "body", in that the color was the same, blueish-green, but each one, remarkibly had numerous arms.   no legs.  ax called them arms, though they were jointed twice and bent in impossible ways.  at the end of each "arm" was a "hand" of 4 "fingers".  each finger was identical.  they were "standing" on arms which came from were legs would have been on any other normal creature.  their mouth was in their chest and there was no distinct head.  a row of black circle flanked each side.  ax reasoned that these were eyes.  ax said that he counted the "arms" of each, but found no relation in each creature.  one had maybe 10, where another had only 4.  the "eyes" were also this way.  the color of the flesh was also varied, some more blue than the rest.
+
+ax was able to get such a good look at them by hiding inside a tree.
+
+ax went on a few more trips, but fond nothing.  a few days before rat arrived, ax prepared some weapons and said he wanted to fight a disorder.  he has not returned.
+
+rat finds the members of the torch caretakers and they tell him that they have not seen the disorder, only seen the movement of grass and trees.  one of them says that ax is crazy, another says that he begged ax to take him with him, but was refused.
+
+at the inn in the first night a few of the other hunters band together and discuss their plans and what they got from talking with the townsfolk.
+
+rat, alone, returns to his room.  the next morning, the mayor approaches rat and says that one of the torch warden has suggested that rat come visit them that night to help and possibly see a disorder.  the other hunters are also invited.
+
+during the first day, rat checks his diary, which contains a to-do list.  on the list is "find apprentices".  once rat has read his diary, he begins to search the town for apprentices.  various townspeople give their recommendations and rat eventually find a torch warden and a cousin of ax to take in.
+
+rat gives them a collection of manuals, each with a note stating that they must never tell anyone what they have read.  rat, unsure if they are suitable, says that they must accompany him on a hunt before they can work with him.
+
+that night, the torch wardens teach rat how to use the torches.  they explain that they are simple larger versions of a simple torch that people use.  there is a oil soaked cloth and a tree that is naturally immune to fire.  for the larger torches, they felled the trees that the branches were normally made of.  once rat is able to prepare and light the large torch, his apprentice give him the materials to create a hand torch.  rat uses the materials to create the torch and the apprentice allows rat to keep it.  the apprentice also gives rat a tool to start the flame on the torch.
+
+that night, only sounds and cries of disorder are heard and the teams walk the perimeter maintaining the torches and keeping the town safe.
+
+over the next few weeks, rat prepares his apprentices by training them in combat and testing them on the manual's content.  by the time his apprentices become strong enough to go on a hunt, reports from the torch wardens indicate the the disorder has been less wary of the torches.  miners from the cave where they were released have not been in the mines, but a few went to the mines anyway and reported that the boulders that they put to block the disorders entrance has been destroyed and rotten traces of the lost livestock were found there.  they also return with a scrap of cloth from ax's shirt.
+
+ax's cousin, visibly upset and filled with courage that has been developed in him from training vows to kill every last disorder and soon runs from the party.
+
+rat and the other party member search the entrance of the cave and find the cousin sitting on a rock facing the tunnel.  cries from the disorder can be heard coming from within.
+
+rat explains to his party that they are ready to go on a hunt. they head back to town and arrive just before dusk.
+
+when they arrive they notice that one of the other hunters is missing. the band says that one of the hunters said he saw something in the forest just south of town and ran after it. he has not returned.
+
+the party splits up and gets some things ready for the hunt at night. the blacksmith gives rat a list of materials and says that rat will be rewarded if he can find them. when rat returns to the inn, the inn keeper approaches rat and gives him some healing potions. after dusk, the party sets out. first they stop by the torch wardens and tell them that they will be leaving.
+
+as they leave the light from the torches, they can hear the cries of the disorder in the distance grow louder. soon, they reach the cave.
+
+the torch warden places a torch at the entrance of the cave. as they are there, they can hear the cries of the disorder. they enter.
+
+as they move through the black mines, they can hear shuffling of feet from all directions. the reflections of sound creates a dizzy environment. they come across a glove on the floor. the cousin picks it up and recognizes it as ax's. overwhelmed again, the cousin darts off into the mine, leaving rat the the torch warden.
+
+rat and the warden attempt to follow the cousin, but he is lost. as they continue, they hear a deep growl and are confronted with a disorder. the beast has patchy black hair, four legs and two arms on the top of his body. they hang over its head and grabs at the warden.
+
+"don't let it touch you!" yells rat. they slay the beast. as the warden approaches the corpse, a gust of hot wind rushes past them and blows out their torches. they are surrounded by the sound of movement on the damp floor. rat quickly relights his torch and when their eyes adjust, the slain creature's body is gone. a trail of blue blood stretches back into the mines.
+
+they party goes to the entrance and find the cousin there. the warden, upset, screams at the cousin. he sits.
+
+the party heads out back toward town. they arrive as the sun begins to rise. once they past the torches, they split up and agree to meet in the morning.
+
+rat is woken by the mayor, who tells him that he heard about the glove and the encounter in the mines. it seems everybody has heard about the glove. when he finds the warden, the warden tells him that he told his parents about the book. rat begins to get upset, but the warden continues. he says that he told them that they need an area to do the things they have learned from the book, and that his parents have agreed to give him some land, secret from the town, so that they are in privacy. rat calms down.
+
+the cousin is sitting at his house. he says that he cannot be part of the party anymore since he cannot control his emotions. he says that he hopes that he can join again, when he is stronger.
+
+the townsfolk comment that a strong storm seems to be coming and that they are worried about the torches. one person says that storms have happened before and the torches were ok, but with one of the disorder slain, and that the storm seems bigger than usual, they are worried the torches might not stay lit.
+
+the mayor has called a meeting and asks that every able-bodied person volunteer to help maintain the torches tonight. the blacksmith has donated several lanterns for the townsfolk to use during the patrol.
+
+that night, rat and the warden are on patrol with the lanterns. a group of hunters arrive in the storm and say that they would like to stay. they head into the town that night.
+
+throughout the night the townsfolk keep the torches burning and they patrol the town in spite of the heavy rain.
+
+at dawn the rain lets up and people start to head back into town.
+
+the hunters that arrived in the night are at the inn. they say that they are "for hire" and that they will set up a small camp outside of town, on the east side, by the lake. they invite rat to come to their camp, even if he is not interested.
+
+when rat runs into the blacksmith, he reminds rat about the list. he says that before ax left, they were working on a lantern that would be much brighter than the lanterns he is able to make now. he thanks rat for helping, stating that he is dissapointed the new group didn't help the town that night. he give rat a lantern.
+
+that day, the storm is much less, and they doesn't seem to be much worry about it.
+
+the cousin isn/t at his house, but his wife says that he went to an area near the lake to practice his swordmanship. rat sets out to find ax, and stops at the camp outside of town. the newcomers greet him and introduce themselves and their rate. rat continues on to the lake and finds the cousin practicing on a large tree truck wrapped with a rope. he is wearing ax's glove.
+
+the cousin says that he needs to let go of the emotions he has and to believe that his cousin is alive. by now its been 2 weeks since he left. rat leaves him to practice. the cousin says that as children they would often go to the falls and jump. he said that ax was never scared of the jump, and that he would jump with his eyes closed, and gracefully fall into the water. he said that when he couldn't get the courage to jump, ax held his hand and they jumped together.
+
+the cousin suggests that they go see the falls.
+
+rat returns.
+
+the warden says the because the rain hasn't completely let up, he will need to assist the other wardens with the torches that night. he says that he has something to show rat the next day.
+
+the next day, rat find the warden and he introduces him to the kennel he started to build. they make plans to go out that night for another hunt.
+
+this time, they decide not to go to the cave, but to follow the other group of hunters who go into the forest to look for their lost companion. as they more through the forest, they find him laying on a tree. he looks dead. even though his is badly injured, he wakes up when the party gets close. the other hunters quickly bring him to his feet, give him water and the party heads back to town.
+
+they take him to the doctor and leave him there in his care. one of the hunters stays there with him.
+
+the following day, rat seeks out the warden and they go into the forest to search for places where the disorder may be staying. as they go through the forest, the encounter corpses of animals. not a good sight.
+
+they head to the site where they found the other hunter. rat notices a trail of blue blood and they decide to follow it. it terminates at the body of a disorder, half alive. there is deep cut in its belly. its intestines lay in the dirt. when they approach it, it gets on it feet. this take s a while, as a long spider-like limbs unwrap themselves and slowly lift its body into a standing position. it has 7. a pair of limbs bring up it's intestines from the ground and they wrap around its torso, like a bandage.
+
+it lets out a weak cry and falls onto the ground, dead.
+
+the party, having watched this, wait then then approach it to verify that it is dead. when they get close, they hear a shuffling in bushes close to the creature and two small cat-sized creatures burst out. they resemble the dead one, but are not completely the same. the proportions didn't seem right.
+
+rat pulls out a bag and yells for the warden to do the same. he warns the warden again not to allow it to touch him. they quickly capture them in the bags. rat explains that they will not be able to tell the others that they have captured the beasts for fear that they would kill them. rat says he will try to find a solution. he takes the two bags and tells the warden that they will have to return to the town.
+
+rat takes the bag of disorder to the place were the warden has built. this will become their secret for now. rat knows that they will have to tell the mayor soon, otherwise they may not be able to stay in the town.
+
+rat finds his notebook and asks the warden for the book that he was supposed to study. they look through the book and make a new entry for the creature that they have found.
+
+for the next week, rat and his apprentices watch as the whelps grow in size. they have been carrying out this in secret, since they know that the townsfolk may not understand. at the end of the week, one of the whelps collapses and lays motionless. it has died.
+
+the other whelp consumes its dead sibling and for the next couple weeks it continues to grow. rat, worried that the large creature will outgrow the area makes plans to move it from the town. he decides to talk to the blacksmith to buy chains to transport it.
+
+when rat returns, the whelp is gone, although there is no trace of damage to the area meant to contain it. there is a hole in the ground and blood around the entrance to it. there is a whimper coming from the hole.
+
+rat walks near it, then the whelp bursts out! rat quickly kills it, but it releases a blood curdling scream before slumping to the ground dead.
+
+there is a sound coming from the hole. it is a smaller version of what rat had just killed.
+
+the mayor and all of the towns folk have by now assembled by the home and pen where the disorder is kept. the mayor is furious about the commotion and the dead creature on the ground, it is obvious that the thing had been penned up there. as he approaches rat the baby creature leaps from the hole and lands between rat and the mayor.
+
+the little creature, not much larger than a kitten begins to growl awkwardly at the mayor as it backs up in between rats legs. the mayor, surprised, backs away and rat bends down to pick the creature up. inside rat's arms, the creature hides is face and stares at the mayor.
+
+the mayor, once realizing that there is no danger, dismisses the crowd and tells rat that he needs to see him the following morning, without the little creature.
+
+the apprentices melt out from the crowd and join rat. there is a new party member!
+
+a new entry is filed in the book, along with notes indicating it's parents, the little whelps they found in the bushes.
+
+the following morning, rat visits the mayor's house and and explains that he encountered these creatures from his home. they are a mutant race, but they can be controlled through breeding and domestication. the little creature has responded to rat because he saw it before its parent was alive and defended him. he explains that many of them are very different from each other but can be bred and tamed. he says that this is the only way to stop them, because the longer they are left alone, the stronger they will become.
+
+the mayor, still upset says that the creatures must be moved out of town and that rat is to not return for a week while he considers if he can come back or not.
+
+when rat returns, he has found that the apprentices have gathered the books and readied them to leave. they also tell him that he cannot come back for a week and that they cannot venture out to find him.
+
+rat sets off that day and heads toward the camp of mercenaries by the lake.
48 docs/TODO
@@ -0,0 +1,48 @@
+lib2d planned features:
+
+8-bit surfaces on the scrolling buffers makes a very fast renderer
+
+allow multiple concurrent gamestates
+integrate dialogs from mh and allow customization(?)
+the sheetloader should be updated to only support the grid format
+the grid utilities should be updated
+
+game objects need a new system that has special handling of pickles:
+ pygame objects cannot be pickled, but the actors can
+ pygame objects need a way to load resources off disk from a pickle
+
+
+why pickles?
+ cause it would not make sense to keep all objects in memory.
+ makes a system of persistence that is built in python
+
+
+When loading only the tree should be available at first.
+
+zombie survival simulation
+random game each time
+hard rules…sandbox?
+score given. play time should be about an hour?
+fallout style missions:
+ random encounters
+ immersive, rather than focused on action
+use of goap ai to simulate npcs
+how about…turn based, rougelike?
+features from movies/tv that should be in game:
+ barricading using objects in room (push chair to wall?)
+ stockpiling items
+ morale bonus for hot shower
+ head shots?
+ health items: anti-biotics, pain pills, steroids, etc
+ ability to use objects as weapons
+ safe houses
+ rumors
+ party forming
+
+
+mega maps:
+divide large maps into regions
+regions can be loaded from disk, complete with geometry
+entities in the game world can request regions be loaded into memory
+renderer should be able to render many regions simutaneously
+the tilesets 'images' can be loaded on the fly without affecting metadata
BIN lib/.DS_Store
Binary file not shown.
1 lib/__init__.py
@@ -0,0 +1 @@
+
361 lib/battlestate.py
@@ -0,0 +1,361 @@
+"""
+
+combat:
+ every party member's spells are available to cast if their cooldown is met.
+ when selected, they will have a countdown until the spell is cast
+ during the cooldown, the opponent has an oportunity to block your spell
+
+combat:
+ each party gets a limited time to select action
+ actions will be presented, give other party option to play a counter spell
+ actions will be played out on a stack
+
+so, it is turn based, but with some realtime elements
+"""
+
+
+from renderer import Camera
+
+from lib2d.gui import VisualTimer
+from lib2d.gamestate import GameState
+from lib2d.cmenu import cMenu
+from lib2d.statedriver import driver as sd
+from lib2d.banner import TextBanner, OutlineTextBanner
+import lib2d.gfx as gfx
+
+from mob import Monster
+from rpg import Hero
+
+from collections import defaultdict, deque
+from math import ceil
+from random import randint
+import sys, os.path
+
+from pygame import Rect, Surface
+import pygame.draw
+
+
+class FadingText(object):
+ """
+ make a nice display for damage given, hp gained, etc
+ """
+
+ speed = .1
+ lenght = 2000
+
+ def __init__(self, text, pos, color = (255,255,255), icon = None):
+ banner = TextBanner(text, color, 12)
+
+ if icon == None:
+ self.image = banner.image
+ else:
+ icon = pygame.transform.scale(icon, (32, 32))
+ r = banner.image.get_rect()
+ r.width += 32
+ r.height = 32
+ self.image = Surface(r.size)
+ self.image.set_colorkey((0,0,0))
+ self.image.blit(icon, (0,0))
+ self.image.blit(banner.image, (32, 0))
+
+ self.rect = self.image.get_rect()
+ self.rect.topleft = pos
+ self.y = float(pos[1])
+ self.time = 0
+ self.alive = 1
+
+
+ def update(self, time):
+ if self.alive:
+ self.time += time
+
+ if self.time < self.lenght:
+ self.y -= self.speed
+ self.rect.top = self.y
+ else:
+ self.alive = 0
+
+
+ def draw(self, surface):
+ if self.alive:
+ surface.blit(self.image, self.rect)
+
+
+class BattleState(GameState):
+ """
+ This state is used to handle combat between the player and a CPU
+ monster.
+
+ The real work is handled by SelectState and StackState.
+ """
+
+ def __init__(self, party, enemies):
+ GameState.__init__(self)
+
+ self.bkg = gfx.load("misc/overpass.png")
+ self.bkg = pygame.transform.scale(self.bkg, (sd.get_size()))
+
+ self.actors = []
+ self.glitter = []
+
+ hero = lib2d.sheetloader.load_actor("hero", Hero)
+ hero.avatar.play("walk")
+ hero.face("north")
+ self.actors.append(hero)
+
+ m0 = Monster()
+ m0.render()
+
+ m0.rect.topleft = (170, 10) # hack
+
+ party = [hero]
+ enemies = [m0]
+
+ self.timer = VisualTimer(0, (10,30,100,16))
+ self.player_area = Rect(160, 140, 160, 78)
+ self.party = [party, enemies]
+ self.member_focus = 0
+
+ self.stale_spells = []
+
+ # kinda a hack until [if] i properly implement a scheduler for
+ # concurrent states
+ self.states = []
+ self.substate = None
+
+
+ def win(self):
+ """
+ Called when the player wins
+ """
+
+ sd.replace(WinningState())
+
+
+ def start(self, state):
+ """
+ hack!
+ """
+
+ self.states.append(state)
+ self.substate = self.states[-1]
+ self.substate.activate()
+
+
+ def done(self):
+ """
+ hack!
+ """
+
+ self.substate.deactivate()
+ self.states.pop()
+ self.substate = self.states[-1]
+ self.substate.activate()
+
+
+ def draw(self, surface):
+ surface.fill((0,0,0))
+ self.substate.draw(surface)
+ self.timer.draw(surface)
+ surface.blit(self.party[1][0].image, (170,10)) # draw the monster
+ [ g.draw(surface) for g in self.glitter ]
+ surface.fill((32,32,32), self.player_area)
+
+
+ def handle_event(self, event):
+ self.substate.handle_event(event)
+
+
+ def activate(self):
+ self.start(SelectState(self))
+
+
+ def update(self, time):
+ """
+ if another state such as select state is here, make sure they are
+ calling this update
+ """
+
+ [ i.update(time) for i in self.glitter ]
+ [ a.avatar.update(time) for a in self.actors ]
+ self.timer.update(time)
+ self.glitter = [ i for i in self.glitter if i.alive ]
+ self.substate.update(time)
+
+
+class SelectState(GameState):
+ """
+ Allow the player to choose action for the battle.
+
+ This game state should be be used with the normal StateDriver
+ """
+
+ select_delay = 10000 # how much time you have before you are skipped
+
+
+ def __init__(self, parent):
+ GameState.__init__(self)
+ self.stack = deque()
+ self.parent = parent
+ self.history = defaultdict(int)
+ self.last_selection = defaultdict(int)
+
+
+ def update_spell_card(self, index):
+ """
+ update the selected spell information
+
+ normally called as a callback from the menu
+ """
+
+ s = Surface((240,32))
+ spell = self.items[index][2]
+ self.render_spell_card(s, spell)
+ self.selected_spell = s
+
+
+ def select_spell(self):
+ """
+ allow the player to choose the target for a spell
+ """
+
+ spell = self.items[self.menu.selection][2]
+ caller = self.parent.party[0][self.parent.member_focus]
+ target = self.parent.party[1][0]
+ self.stack.append((spell, caller, target))
+ self.last_selection[caller] = self.menu.selection
+ self.history[(caller, self.menu.selection)] += 1
+
+ # HACK
+ self.parent.start(StackState(self.parent, self.stack))
+
+ def render_spell_card(self, surface, spell):
+ """
+ render a spell card on the screen
+ """
+
+ position = (0, 0)
+ icon = spell.icon
+ surface.blit(icon, position)
+ rect = icon.get_rect()
+ text = TextBanner(spell.description, (255,255,255), size=14)
+ text.render()
+ position = (rect.width + 8, rect.height - text.rect.height)
+ surface.blit(text.image, position)
+
+
+ def activate(self):
+ import spells
+
+ def name(spell):
+ if spell.uses >= 1:
+ i = loaded_spells.index(spell)
+ return "{0} ({1})".format(
+ spell.name,
+ spell.uses - self.history[(caller, i)])
+
+ else:
+ return spell.name
+
+ caller = self.parent.party[0][self.parent.member_focus]
+
+ loaded_spells = spells.load("all")
+
+ self.items = [ (name(s), self.select_spell, s) for s in loaded_spells ]
+ self.menu = cMenu(Rect((10,60),sd.get_size()),
+ 20, 0, 'vertical', 100,
+ self.items,
+ font="fonts/dpcomic.ttf", font_size=16,
+ banner_style="normal")
+
+ self.menu.callback = self.update_spell_card
+
+ for i, spell in enumerate(loaded_spells):
+ if spell.uses >= 1:
+ if self.history[(caller, i)] >= spell.uses:
+ self.menu.disable(i)
+
+ self.menu.ready(self.last_selection[caller])
+ self.update_spell_card(self.menu.selection)
+ self.parent.timer.set_alarm(self.select_delay)
+
+
+ def update(self, time):
+ if self.parent.timer.finished:
+ self.parent.start(StackState(self.parent, self.stack))
+
+
+ def handle_event(self, event):
+ self.menu.handle_event(event)
+
+
+ def draw(self, surface):
+
+ if not self.selected_spell == None:
+ surface.blit(self.selected_spell, (0,208))
+
+ self.menu.draw(surface)
+
+
+class StackState(GameState):
+ """
+ Simple state that shows the player spells on the stack
+ and processes it
+
+ This game state should be be used with the normal StateDriver
+ """
+
+ stack_delay = 1000
+
+
+ def __init__(self, parent, stack):
+ GameState.__init__(self)
+ self.parent = parent
+ self.stack = stack
+
+
+ def activate(self):
+ self.parent.timer.set_alarm(self.stack_delay)
+
+
+ def update(self, time):
+ if self.parent.timer.finished:
+ self.parent.timer.reset()
+
+ try:
+ spell, caller, target = self.stack.popleft()
+ except IndexError:
+ pass
+ else:
+
+ spell.cast(caller, target)
+
+ g = FadingText(
+ spell.text,
+ target.rect.center,
+ icon = spell.icon)
+
+ self.parent.glitter.append(g)
+
+ if target.hp <=0 :
+ target.isAlive = False
+
+ if not spell.finished:
+ self.parent.stale_spells.append((spell, caller, target))
+
+ if not [ e for e in self.parent.party[1] if e.isAlive ]:
+ self.parent.win()
+
+ if len(self.stack) == 0:
+ [ self.stack.append(i) for i in self.parent.stale_spells ]
+ self.parent.stale_spells = []
+ self.parent.done()
+
+
+class WinningState(GameState):
+ """
+ State for a win after a battle
+ """
+
+ def activate(self):
+ sd.done()
14 lib/conditions.py
@@ -0,0 +1,14 @@
+# conditions are modifications to a game object
+NORMAL = 0
+POISONED = 1
+TIRED = 2
+BLEEDING = 4
+SLEEPING = 8
+
+
+icons = [
+(6,4),
+(19,2),
+(4,5),
+(6,0),
+(2, 29) ]
159 lib/cutscene.py
@@ -0,0 +1,159 @@
+"""
+Copyright 2010, 2011 Leif Theden
+
+
+This file is part of lib2d.
+
+lib2d is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+lib2d is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with lib2d. If not, see <http://www.gnu.org/licenses/>.
+"""
+
+from lib2d.gamestate import GameState
+from lib2d.buttons import *
+from lib2d.statedriver import driver as sd
+from lib2d import res
+import pygame
+
+from dialog import TextDialog
+
+import os.path
+
+
+
+class Cutscene(GameState):
+ """
+ This state can read a text file and make a series of dialogs from it.
+
+ Each individual dialog must be it's own line.
+ Blank lines are ignored.
+ Comments may be added using a #. Inline comments are not supported.
+
+ Special commands are available:
+
+ #image:filename load the image and blit it
+ #image-colorkey:filename load image and use top-left corner as colorkey
+ #image-alpha:filename load image with per-pixel alpha
+
+ if you add a "+" before the filename, the image will be scaled to fit the
+ screen
+
+ """
+
+ def __init__(self, path):
+ GameState.__init__(self)
+
+ self.dialogs = []
+
+ with open(path) as fh:
+ for line in fh:
+ line = line.strip()
+ if line != "":
+ self.dialogs.append(line)
+
+ self.dialogs.reverse()
+
+ self.queue_image = None
+ self.queue_music = None
+ self.queue_dialog = None
+
+ def deactivate(self):
+ GameState.deactivate(self)
+ res.fadeoutMusic()
+
+ def activate(self):
+ GameState.activate(self)
+
+ # this is hack, for sure, but ensure our music will start
+ # playing if we are being transition'd from another state
+
+ if self.dialogs[-1][:6] == "#music":
+ text = self.dialogs.pop()
+ tag, path = text.split(":")
+ #res.playMusic(path, loops=-1)
+
+ self.cleared = False
+
+ def reactivate(self):
+ GameState.reactivate(self)
+ self.cleared = False
+
+ def draw(self, surface):
+ if not self.cleared:
+ surface.fill((0,0,0))
+ self.cleared = True
+
+ if self.queue_image != None:
+ surface.blit(*self.queue_image)
+
+ def update(self, time):
+ if self.queue_dialog:
+ if self.cleared:
+ sd.start(self.queue_dialog)
+ self.queue_dialog = None
+ self.cleared = False
+
+ else:
+
+ try:
+ text = self.dialogs.pop()
+ except IndexError:
+ sd.done()
+ return
+
+ if text[:6] == "#image":
+ fill = False
+ tag, path = text.split(":")
+
+ if path[0] == "+":
+ fill = True
+ path = path[1:]
+
+ if tag[-8:] == "colorkey":
+ image = res.loadImage(path, colorkey=1)
+ elif tag[-5:] == "alpha":
+ image = res.loadImage(path, alpha=1)
+ else:
+ image = res.loadImage(path)
+
+ rect = image.get_rect()
+ if fill:
+ size = [ int(i) for i in sd.get_size() ]
+ x = size[0] - rect.w
+ y = size[1] - rect.h
+ r = float(rect.w) / rect.h
+ if x > y:
+ rect = pygame.Rect((0,0,size[0],size[1]*r))
+ else:
+ rect = pygame.Rect((0,0,size[0]*r,size[1]))
+
+ rect.center = size[0] / 2, size[1] / 2
+ image = pygame.transform.smoothscale(image, rect.size)
+ else:
+ rect.topleft = ((sd.get_size()[0]/2)-(rect.width/2), 10)
+
+ self.queue_image = (image, rect)
+ self.cleared = False
+
+ elif text[:6] == "#music":
+ tag, path = text.split(":")
+ self.queue_music = path
+
+ else:
+ self.queue_dialog = TextDialog(text)
+
+
+ def handle_event(self, event):
+ pass
+
+ def handle_commandlist(self, cmdlist):
+ pass
239 lib/dialog.py
@@ -0,0 +1,239 @@
+"""
+Copyright 2010, 2011 Leif Theden
+
+
+This file is part of lib2d.
+
+lib2d is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+lib2d is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with lib2d. If not, see <http://www.gnu.org/licenses/>.
+"""
+
+from lib2d.buttons import *
+from lib2d.banner import TextBanner, OutlineTextBanner
+from lib2d.cmenu import cMenu
+from lib2d.gamestate import GameState
+from lib2d.statedriver import driver as sd
+from lib2d import res, gui
+
+from pygame.locals import *
+from pygame.surface import Surface
+import pygame.draw as draw
+import pygame
+
+from textwrap import wrap
+
+
+class TextDialog(GameState):
+ """
+ State that takes focus and waits for player to press a key
+ after displaying some some text.
+ """
+
+ # works well for the defualt font and size
+ wrap_width = 44
+
+ wait_sound = res.loadSound("select0.wav")
+ wait_sound.set_volume(0.40)
+
+
+ def __init__(self, text, title=None):
+ GameState.__init__(self)
+ self.font = "dpcomic.ttf"
+ self.text = text
+ self.title = title
+ self.blank = True
+
+
+ # when given focus for first time
+ def activate(self):
+ from lib2d.gui import GraphicBox
+ self.border = GraphicBox("dialog2.png")
+
+
+ def draw(self, surface):
+ if self.blank:
+ self.blank = False
+
+ sw, sh = surface.get_size()
+ fontSize = int(0.0667 * sh)
+
+ # draw the border
+ x, y = 0.0313 * sw, 0.6667 * sh
+ w, h = 0.9375 * sw, 0.2917 * sh
+ self.border.draw(surface, (x, y, w, h))
+
+ fullpath = res.fontPath("dpcomic.ttf")
+ font = pygame.font.Font(fullpath, fontSize)
+
+ # adjust the margins of text if there is a title
+ if self.title:
+ x = 0.0625 * sw
+ y = 0.7 * sh
+
+ gui.drawText(surface, self.text, (0,0,0), (x+10,y+8,w-18,h-12),
+ font, aa=1, bkg=self.border.background)
+
+ # print the title
+ if self.title != None:
+ banner = OutlineTextBanner(self.title, (200,200,200),
+ int(fontSize*1.25), font=self.font)
+ title_image = banner.render()
+ x, y = 0.4688 * sw, 0.625 * sh
+ surface.blit(title_image, (x, y))
+
+ # show arrow
+ #x, y = 0.0625 * sw, 0.9167 * sh
+ #arrow = res.loadImage("wait_arrow.png", colorkey=1)
+ #surface.blit(arrow, (x, y))
+
+ # play a nice sound
+ self.wait_sound.stop()
+ self.wait_sound.play()
+
+
+ def handle_commandlist(self, cmdlist):
+ for cls, cmd, arg in cmdlist:
+ if arg == BUTTONDOWN and cmd == P1_ACTION1 and not self.blank:
+ sd.done()
+
+
+class ChoiceDialog(GameState):
+ #wrap_width = 58 # font 12
+
+ text_size = 14
+ wrap_width = 46
+
+ background = (128, 128, 128)
+
+ wait_sound = res.loadSound("select0.wav")
+ wait_sound.set_volume(0.20)
+
+ def __init__(self, text, choices, title=None):
+ GameState.__init__(self)
+ self.text = text
+ self.state = 0
+ self.counter = 0
+ self.title = title
+ self.choices = choices
+
+ # when given focus for first time
+ def activate(self):
+ xsize = 300
+ ysize = 70
+ bkg = Surface((xsize, ysize))
+ bkg.lock()
+ bkg.fill((128,128,128))
+ for i in range(1, 4):
+ draw.rect(bkg,(i*32,i*32,i*32),(4-i,4-i,xsize+(i-4)*2,ysize+(i-4)*2),3)
+
+ corner = (64,64,64)
+ bkg.set_at((0,0), corner)
+ bkg.set_at((xsize,0), corner)
+ bkg.set_at((xsize,ysize), corner)
+ bkg.set_at((0,ysize), corner)
+
+ bkg.unlock()
+
+ bkg.set_alpha(64)
+
+ self.bkg = bkg
+
+ if self.title != None:
+ banner = OutlineTextBanner(self.title, (200,200,200), 20)
+ self.title_image = banner.render()
+ self.title_image.set_alpha(96)
+
+ self.arrow = res.loadImage("wait_arrow.png", colorkey=1)
+
+ # when focus is given again
+ def reactivate(self):
+ pass
+
+ # when losing focus
+ def deactivate(self):
+ pass
+
+ def draw(self, surface):
+ # fade in the dialog box background
+ if self.state == 0:
+ surface.blit(self.bkg, (10,160))
+ self.counter += 1
+ if self.counter == 6:
+ self.bkg.set_alpha(0)
+ self.bkg = self.bkg.convert()
+ elif self.counter == 7:
+ surface.fill((128,128,128), (14, 146, self.bkg.get_size()))
+ print "fill"
+ self.counter = 0
+ self.state = 1
+ self.bkg = None
+
+ # fade in the title, if any
+ elif self.state == 1:
+ if self.title != None:
+ surface.blit(self.title_image, (15,150))
+ self.counter += 1
+ if self.counter == 3:
+ self.state = 2
+ self.counter = 0
+ self.title_image = None
+ else:
+ self.state = 2
+
+ # quickly write the text
+ elif self.state == 2:
+ x = 20
+
+ if self.title != None:
+ y = 168
+ else:
+ y = 167
+
+ for line in wrap(self.text, self.wrap_width):
+ banner = TextBanner(line, size=self.text_size)
+ surface.blit(banner.render(self.background), (x,y))
+ y += banner.font.size(line)[1]
+
+ self.menu = cMenu(Rect((25,210),(280, 30)),
+ 5, 5, 'horizontal', 10,
+ [('Yes', self.yes),
+ ('No', self.no)],
+ font="fonts/dpcomic.ttf", font_size=16)
+
+ self.menu.ready()
+ self.wait_sound.stop()
+ self.wait_sound.play()
+ self.state = 3
+
+ elif self.state == 3:
+ self.menu.draw(surface)
+
+ def handle_event(self, event):
+ if self.state > 2:
+ self.menu.handle_event(event)
+
+ def handle_commandlist(self, cmdlist):
+ for cls, cmd, arg in cmdlist:
+ if (cmd == P1_ACTION1) and arg:
+ sd.done()
+
+ def update(self, time):
+ pass
+
+ def choice0(self): pass
+ def choice1(self): pass
+ def choice2(self): pass
+ def choice3(self): pass
+
+ def yes(self): sd.done()
+ def no(self): sd.done()
12 lib/gui.py
@@ -0,0 +1,12 @@
+"""
+various things for the gui
+"""
+
+
+class TimeDisplay(object):
+ def update(self, time):
+ self.time += time
+
+
+ def draw(self, surface):
+ print self.time
277 lib/mdna.py
@@ -0,0 +1,277 @@
+
+"""
+Mobs in the world all are genetic decendants of an alpha and beta monster.
+
+all mobs have a body, skin, and mouth. everything else is optional.
+the other traits if no present, may randomly be added to
+offspring.
+
+gene codes/modifier are even, gene value and gene is odd
+
+gene code and modifers are not processed, although
+is possible the offspring may aquire a gene from a parent
+
+value is aquired by ANDing together
+
+skin, body, and mouth traits will always be the first 6 bytes
+
+lower generation creatures have a smaller code to work with
+as monster generations grow, they have larger code for qualities
+
+off spring are created by bit operations
+
+random bits may be flipped, creating very different monsters
+
+bits have a higher chance of persiting, rather than not
+since the opperation is AND on the bits.
+
+this creates a situation where eventually, the dna will be completely full
+
+"""
+
+gene_code = {
+"bodyType" : 0,
+"bodyDex" : 1,
+"bodyStr" : 2,
+"bodySize" : 3,
+
+"skinColor" : 4,
+"skinTexture" : 5,
+"skinHair" : 6,
+"skinPoison" : 7,
+
+"mouthNum" : 8,
+"mouthLocation" : 9,
+"mouthPoison" : 10,
+"mouthSpit" : 11,
+"mouthSpitDamage" : 12,
+"mouthBiteDamage" : 13,
+"mouthType" : 14,
+
+"limbNum" : 15,
+"limbLocation" : 16,
+"limbLength" : 17,
+"limbWidth" : 18,
+"limbTexture" : 19,
+"limbType" : 20,
+"limbStrength" : 21,
+"limbGrasp" : 22,
+"limbDamage" : 23,
+
+"earNum" : 24,
+"earLocation" : 25,
+"earSkill" : 26,
+"earType" : 27,
+
+"eyeNum" : 28,
+"eyeLocation" : 29,
+"eyeSkill" : 30,
+"eyeType" : 31,
+
+"headType" : 32,
+"headLocation" : 33,
+"headHorns" : 34,
+
+"brainInt" : 35,
+"brainSpeak" : 36,
+
+"toughness" : 37,
+"spirit" : 38,
+
+"sexDrive" : 39,
+"sexPotency" : 40,
+
+"wings" : 41
+}
+
+gene_code_r = dict([(v, k) for (k, v) in gene_code.iteritems()])
+
+import struct
+import random
+import math
+import base64
+from gzip import GzipFile
+from StringIO import StringIO
+
+class mDNA(object):
+ def __init__(self, d, g):
+ """
+ i tried to make this a system that could include the ability for
+ resessive gene traits.
+
+ a gene can onyl be expressed in the create if the gene is active.
+ each gene has a value that when combined with the proper partners
+ can cause the gene to become active
+ """
+
+ self.d = d
+ self.g = g
+
+ @staticmethod
+ def deconstruct(mdna):
+ """
+ given a mDNA sequence, return a proper mDNA data
+
+ d = value of the gene
+ g = sequence that determines heiredity
+ """
+
+ d = {}
+ g = {}
+
+ for i in range(len(mdna)):
+ if i % 2 == 0:
+ key = gene_code_r[ord(mdna[i])]
+ else:
+ v = ord(mdna[i])
+ d[key] = v >> 4
+ g[key] = v & 15
+
+ return d, g
+
+ @staticmethod
+ def create_gene(code, mod, her):
+ """
+ create a byte sequence that represents this gene sequence
+
+ code = gene code
+ mod = modifier
+ her = ???
+ """
+
+ if mod > 15: mod = 15
+ if her > 15: mod = 15
+ return chr(code) + chr(mod << 4 | her)
+
+ def mate(self, *parents):
+ """
+ mate this mDNA with another parent, or parents.
+ return a new mDNA object
+ """
+
+ parents = list(parents)
+ parents.append(self)
+ parents = [p.serialize() for p in parents]
+
+ d = {}
+ g = {}
+
+ for mdna in parents:
+ for i in range(len(mdna)):
+ if i % 2 == 0:
+ key = gene_code_r[ord(mdna[i])]
+ else:
+ v = ord(mdna[i])
+ mod = v >> 4
+ genome = v & 15
+ if key in d.keys():
+ dice = random.randint(0,4)
+ if dice == 0:
+ g[key] = g[key] | genome
+ if dice == 1:
+ AND = g[key] & genome
+ OR = g[key] | genome
+ g[key] = AND | OR
+ if dice == 2:
+ pass
+ if dice == 3:
+ g[key] = genome
+ dice = random.randint(0,2)
+ if dice == 0:
+ pass
+ if dice == 1:
+ d[key] = mod
+ if dice == 2:
+ d[key] = int(math.ceil((d[key] + mod) / 2))
+ else:
+ d[key] = mod
+ g[key] = genome
+
+ return mDNA(d, g)
+
+ def serialize(self):
+ # return a string of characters that represents this mDNA
+ mdna = ""
+ for k, v in self.d.items():
+ code = gene_code[k]
+ mdna += self.create_gene(code, v, self.g[k])
+ return mdna
+
+ def tag(self):
+ """
+ return a compressed version of the mDNA sequence in base64
+ """
+
+ #sio = StringIO()
+ #gz = GzipFile(fileobj=sio, mode='wb')
+ #gz.write(self.serialize())
+ #gz.close()
+ print self.serialize()
+ return base64.b64encode(self.serialize())
+
+ def mutate(self, chance=100):
+ """
+ mutate the mDNA by flipping some bits in the the sequence
+ """
+
+ mdna = self.serialize()
+ mutant = ""
+ for i in mdna:
+ if random.randint(0,chance) == 0:
+ v = ord(i)
+ v = v & random.randint(0,15)
+ mutant = mutant + chr(v)
+ else:
+ mutant += i
+
+ self.d, self.g = mDNA.deconstruct(mutant)
+ print self.d
+
+
+def random_beast():
+ mdna = ""
+ for k, v in gene_code.items():
+ mdna += mDNA.create_gene(v, random.randint(0,3), random.sample([0,1,2,4],1)[0])
+ return mDNA(*mDNA.deconstruct(mdna))
+
+def calc_score(mdna):
+ # used to judge one's gene's over another's
+ l = len(mdna)
+ score = 0.0
+ genes = 0.0
+ for i in range(l):
+ if i % 2 == 0:
+ pass
+ else:
+ score += ord(mdna[i])
+ genes += (ord(mdna[i]) & 15)
+
+ score = int(score / (l/2))
+ genes = int(genes / (l/2))
+ return score, genes
+
+
+if __name__ == "__main__":
+
+ alpha = random_beast()
+ beta = random_beast()
+ gamma = random_beast()
+ delta = random_beast()
+ population = [alpha, beta, gamma, delta]
+
+ for i in range(4):
+ print "generation %d:" % (i + 1)
+ for i in range(0, int(math.ceil(len(population) * 0.80))):
+ l = len(population) - 1
+ if l >= 50:
+ for x in range(int(l - (l*.10))):
+ population.pop()
+ l = len(population) - 1
+
+ b0 = population[random.randint(0,l)]
+ b1 = population[random.randint(0,l)]
+ offspring = b0.mate(b1)
+ if random.randint(0,10)==0:
+ offspring.mutate()
+ population.append(offspring)
+ print offspring.tag()
31 lib/misc.py
@@ -0,0 +1,31 @@
+from lib2d import GameState
+from lib2d.banner import TextBanner
+
+
+
+class LoadingScreen(GameState):
+ """
+ Class that displays a simple screen to let the player know the game is
+ beling loaded in the background.
+ """
+
+ def __init__(self, parent):
+ self.parent = parent
+ self.activated = False
+
+
+ def activate(self):
+ self.blank = True
+
+
+ def draw(self, surface):
+ if blank:
+ surface.fill((0,0,0))
+ msg = TextBanner("Please wait...", (128, 128, 128), size=18)
+ surface.blit(msg.render(alpha=True), (200,210))
+ self.blank = False
+
+
+ def update(self, time):
+ self.parent.update(time)
+
435 lib/mob.py
@@ -0,0 +1,435 @@
+"""
+
+
+"""
+
+from rpg import LivingObject
+from mdna import mDNA, random_beast
+
+from lib2d.vec import Vec2d
+
+from pygame.surface import Surface
+from pygame.rect import Rect
+from pygame import draw, Color
+
+from math import cos, sin, radians, degrees
+from random import randint
+
+
+raw_colors = [(125,32,40), (32,64,128), (20,20,200), (200,200,40), \
+ (134,54,231), (234,43,90), (80,1,84)]
+
+colors = []
+[ colors.append(Color(i[0], i[1], i[2])) for i in raw_colors ]
+
+
+"""
+this is a super basic set of functions for animation skeletons
+not ready yet...
+"""
+
+def padimage(self, image):
+ """
+ Do a little processing of the input image to make it purdyer.
+ Pad the image with transparent pixels so the edges get antialised
+ when rotated and scaled. Looks real nice.
+ """
+
+ new = Surface(image.get_rect().inflate(2, 2).size, pygame.SRCALPHA)
+ color = image.get_at((0,0))
+ color[3] = 0
+ new.fill(color)
+ new.blit(image, (1,1))
+ return new
+
+
+class Body(object):
+ """
+ a rigid body sprite
+ """
+
+ def __init__(self, rect, image):
+ self.rect = rect
+ self.normal_image = image
+ self.image = None
+ self.angle = 0.0
+ self.old_trans = None
+
+ def update_rect(self):
+ """
+ position the rect to whatever the physics engine says it should be
+ updates the image and rect based on rotation of the physics sprite
+ """
+
+ if slow_pc:
+ angle = round(int(degrees(self.angle) % 360.0 / 360.0 * simple_no) * simple_angle, 2)
+ else:
+ angle = degrees(self.angle) % 360.0
+
+ if angle != self.old_trans:
+ old_trans = angle
+ self.image = rotozoom(self.normal_image, angle, 1)
+ self.rect = self.image.get_rect()
+
+class Line(object):
+ """
+ 2 points
+ """
+
+ def __init__(self, v0, v1):
+ self.v0 = v0
+ self.v1 = v1
+
+
+ def joinWith(self, other, angle):
+ self.rotate(angle)
+
+ def rotate(self, angle):
+ # rotate v1 around v0
+ pass
+
+class Joint(object):
+ """
+ binds two bodies together
+ """
+
+ def __init__(self, body0, body1, position):
+ self.body0 = body0
+ self.body1 = body1
+ self.position = position
+ self.angle = 0.0
+
+
+class Skeleton(object):
+ """
+ a single connected set of bodies and joints
+ """
+
+ def __init__(self):
+ self.parts = []
+
+ def join(self, part0, part1, position):
+ j = Joint(part0, part1, position)
+
+
+def skinColor(mod):
+ return colors[mod["skinColor"]]
+
+
+def hsv(color, hsva):
+ """
+ return a new Color object with the HSV values adjusted
+
+ pygame also includes an alpha channel (a)
+
+ since i want to make simple gradations of value when
+ rendering the physical bodies, we need a simple way
+ to adjust the HSV values of a color. the pygame
+ Color objects have some functionality, but are missing
+ features like this one that we are producing here.
+ """
+
+ new = Color(color.r, color.g, color.b, color.a)
+ h, s, v, a = new.hsva
+ h += hsva[0]
+ s += hsva[1]
+ v += hsva[2]
+ a += hsva[3]
+ new.hsva = (h,s,v,a)
+
+def head(surface, mod, pos):
+ # adjust for body size
+ radius = ((mod["bodySize"] + 1) * 4) + 10
+
+ color = skinColor(mod)
+
+ draw.circle(surface, (0,0,0), pos, radius)
+ draw.circle(surface, color, pos, int(radius * .90))
+ return Rect(pos[0] - radius, pos[1] - radius, radius * 2, radius * 2)
+
+def body(surface, mod, pos):
+
+ # adjust for body size
+ size = ((mod["bodySize"] + 1) * 6) + 30
+
+ # get the correct skin color
+ color0 = skinColor(mod)
+
+ t = mod["bodyType"]
+
+ # wide ellipse
+ if t == 0:
+ rect = Rect(pos,(size, size + size / 3))
+ draw.ellipse(surface, color0, rect)
+ return rect
+
+ # wide curved
+ elif t == 1:
+ radius = size / 2
+ x = pos[0] + radius
+ y = pos[1] + radius
+ draw.circle(surface, color0, (x - radius / 2, y), radius)
+ draw.circle(surface, color0, (x + radius / 2, y), radius)
+ rect = Rect((pos[0] - radius / 2, pos[1]),(size + radius, radius * 2))
+ return rect
+
+ # wide 3-segmented
+ elif t == 2:
+ segments = 3
+ radius = size / 3
+ dx = size / segments
+ x = pos[0] + radius
+ y = pos[1] + radius
+ for i in range(0,3):
+ draw.circle(surface, color0, (x + dx * i, y), radius)
+ rect = Rect((pos[0] - dx, pos[1]), (size + radius, radius * 2))
+ return rect
+
+ # tall ellipse
+ if t == 3:
+ rect = Rect(pos,(size + size / 3, size))
+ draw.ellipse(surface, color0, rect)
+ return rect
+
+ # tall curved
+ elif t == 4:
+ radius = size / 2
+ x = pos[0] + radius
+ y = pos[1] + radius
+ draw.circle(surface, color0, (x, y - radius / 2), radius)
+ draw.circle(surface, color0, (x, y + radius / 2), radius)
+ rect = Rect((pos[0], pos[1] - radius / 2),(radius * 2, size + radius))
+ return rect
+
+ # tall 3-segmented
+ elif t == 5:
+ segments = 3
+ radius = size / 3
+ dy = size / segments
+ x = pos[0] + radius
+ y = pos[1] + radius
+ for i in range(0,3):
+ draw.circle(surface, color0, (x + dx * i, y + dy * i), radius)
+ rect = Rect((pos[0], pos[1]- dy), (radius * 2, radius + size))
+ return rect
+
+ # block
+ else:
+ rect = Rect(pos,(size,size))
+ draw.rect(surface, color0, rect)
+ return rect
+
+def mouth(surface, mod, pos):
+ pos = list(pos)
+
+ if mod["mouthPoison"] < 2:
+ color0 = (255,0,0)
+ color1 = (200,0,0)
+ else:
+ color0 = (255,0,200)
+ color1 = (200,0,200)
+
+ # adjust for body size
+ radius = ((mod["bodySize"] + 1) * 2) + 6
+
+ t = mod["mouthType"]
+ if t == 0:
+ draw.circle(surface, color0, pos, radius)
+ draw.circle(surface, color1, pos, radius, 2)
+ return Rect(pos[0] - radius, pos[1] - radius, radius * 2, radius * 2)
+ else:
+ rect = Rect((pos[0] - radius, pos[1] - radius / 2), (radius * 2, radius))
+ draw.ellipse(surface, color0, rect)
+ draw.ellipse(surface, color0, rect, 2)
+ return rect
+
+class MonsterPart(object):
+ required = []
+ image = Surface((10,10))
+
+ colorkey = (12,13,12)
+
+ def __init__(self, mod):
+ self.disabled = False
+ self.rect = self.image.get_rect()
+ self.mod = mod
+
+ def check(self, mod=None):
+ if mod == None: mod = self.mod
+ for r in self.required:
+ if r not in mod.keys():
+ self.disabled = True
+
+ def update(self, time):
+ pass
+
+ def render(self):
+ pass
+
+class MonsterEye(MonsterPart):
+ required = ["eyeType", "eyeSkill", "skinColor"]
+
+ def render(self):
+ self.check()
+ if self.disabled: return
+
+ pos = self.rect.center
+
+ t = self.mod["eyeType"]
+
+ color0 = (255,255,255)
+ color1 = (0,0,0)
+
+ radius = (self.mod["eyeSkill"] + 2) * 3
+
+ color = skinColor(self.mod)
+
+ # we have to determine how big the eye will be before drawing
+ size = (radius * 2, radius * 2)
+ rect = Rect((0,0), size)
+
+ image = Surface(size)
+ image.fill(self.colorkey)
+ image.set_colorkey(self.colorkey)
+
+ # locking the surface makes multiple drawing operations quicker
+ image.lock()
+
+ # draw the border of the eye
+ if radius < 10:
+ steps = 16
+ else:
+ steps = 8
+
+ for t in range(0,360,steps):
+ t = radians(t)
+ new_color = Color(color.r, color.g, color.b)
+ h, s, v, a = new_color.hsva
+ v = int(sin(t) * 50) + 50
+ if v < 0: v = 0 - v
+ new_color.hsva = (h, s, v, a)
+ x = int(rect.centerx + cos(t) * (radius - 4))
+ y = int(rect.centery + sin(t) * (radius - 4))
+ draw.circle(image, new_color, (x, y), 3)
+
+ # draw the white and pupil
+ draw.circle(image, color0, rect.center, radius - 3)
+ draw.circle(image, color1, rect.center, (radius - 3) / 3)
+
+ image.unlock()
+
+ rect.center = pos
+
+ self.rect = rect
+ self.image = image
+
+def limb(surface, mod, pos):
+ w = ((mod["limbLength"] + 3) * 2) + ((mod["limbWidth"] + 1) * 2)
+ l = (mod["limbLength"] + 3) * 8
+
+ color = skinColor(mod)
+
+ t = mod["limbType"]
+
+ # single jointed limb
+ if t == 0:
+ mid = (l/2) + (mod["limbWidth"] * 2)
+ draw.line(surface, (0,0,0), pos, (pos[0] + w, pos[1] + mid), 8)
+ draw.line(surface, (0,0,0), (pos[0] + w, pos[1] + mid), (pos[0], pos[1] + l), 8)
+ draw.line(surface, color, pos, (pos[0] + w, pos[1] + mid), 4)
+ draw.line(surface, color, (pos[0] + w, pos[1] + mid), (pos[0], pos[1] + l), 4)
+ return Rect(pos, (w,l))
+
+ # straight limb
+ else:
+ rect = Rect(pos,(w,l))
+ draw.rect(surface, color, rect)
+ draw.rect(surface, (0,0,0), rect, 3)
+ return rect
+
+class Monster(LivingObject):
+ """
+ this is a monster.
+ """
+
+ def __init__(self):
+ LivingObject.__init__(self)
+ self.mDNA = random_beast()
+
+ # hold the "parts" (kinda like sprites) attached the the monster
+ self.parts = []
+
+ self.rect = None
+ self.render()
+
+ def render(self):
+ """
+ generate an image suitable to represnt this monster.
+ return Rect(pos[0] - radius, pos[1] - radius, radius * 2, radius * 2)
+
+ the image will be used in battle screens
+ """
+
+ d = self.mDNA.d
+ color = (200,200,200)
+
+ scratch = Surface((150, 150))
+ s = Surface((150,150))
+ s.lock()
+
+ body_rect = body(s, d, (40,40))
+
+ #draw.rect(s, color, body_rect, 2)
+
+ if d["limbNum"] > 0:
+
+ # draw one limb to see how big it is
+ limb_rect = limb(scratch, d, (0, 0))
+
+ t = d["limbLocation"]
+ if t == 0:
+ x, y = body_rect.bottomleft
+ y -= body_rect.height / 4
+ else:
+ x, y = body_rect.midleft
+
+ dx = body_rect.width / d["limbNum"]
+ for i in range(0, d["limbNum"]):
+ limb_rect = limb(s, d, (x, y))
+ x += dx
+
+ t = d["headLocation"]
+ if t == 0:
+ x, y = body_rect.midleft
+ elif t == 1:
+ x, y = body_rect.topleft
+ y -= body_rect.height / 4
+ else:
+ x, y = body_rect.topleft
+
+ head_rect = head(s, d, (x, y))
+ x, y = head_rect.center
+ y = y + head_rect.height / 6
+ mouth_rect = mouth(s, d, (x, y))
+
+ t = d["eyeLocation"]
+ if t == 0:
+ x, y = head_rect.center
+ y -= head_rect.height / 4
+ else:
+ x, y = head_rect.topleft
+
+