├── src
├── images
│ ├── u5
│ │ ├── __init__.py
│ │ ├── grass
│ │ │ ├── __init__.py
│ │ │ ├── grass.png
│ │ │ └── grass2.png
│ │ ├── orc.png
│ │ ├── rat.png
│ │ ├── ankh.png
│ │ ├── arrow.png
│ │ ├── barrel.png
│ │ ├── body.png
│ │ ├── chest.png
│ │ ├── damage.png
│ │ ├── door.png
│ │ ├── ettin.png
│ │ ├── gore.png
│ │ ├── peaks.png
│ │ ├── planks.png
│ │ ├── player.png
│ │ ├── purse.png
│ │ ├── rocks.png
│ │ ├── sand.png
│ │ ├── snake.png
│ │ ├── spider.png
│ │ ├── swamp.png
│ │ ├── sword.png
│ │ ├── tile.png
│ │ ├── tree.png
│ │ ├── well.png
│ │ ├── firebolt.png
│ │ ├── healbolt.png
│ │ ├── mountain.png
│ │ ├── rainbolt.png
│ │ ├── reticule.png
│ │ ├── villager.png
│ │ ├── bard
│ │ │ ├── bard0.png
│ │ │ ├── bard1.png
│ │ │ ├── bard2.png
│ │ │ ├── bard3.png
│ │ │ └── __init__.py
│ │ ├── brush
│ │ │ ├── brush.png
│ │ │ ├── forest.png
│ │ │ ├── jungle.png
│ │ │ └── __init__.py
│ │ ├── floor-brick.png
│ │ ├── foothills.png
│ │ ├── mage
│ │ │ ├── mage0.png
│ │ │ ├── mage1.png
│ │ │ ├── mage2.png
│ │ │ ├── mage3.png
│ │ │ └── __init__.py
│ │ ├── magicbolt.png
│ │ ├── tree-dead.png
│ │ ├── wall-brick.png
│ │ ├── wall-stone.png
│ │ ├── glazed-floor.png
│ │ ├── water
│ │ │ ├── water0.png
│ │ │ ├── water1.png
│ │ │ ├── water10.png
│ │ │ ├── water11.png
│ │ │ ├── water12.png
│ │ │ ├── water13.png
│ │ │ ├── water14.png
│ │ │ ├── water15.png
│ │ │ ├── water2.png
│ │ │ ├── water3.png
│ │ │ ├── water4.png
│ │ │ ├── water5.png
│ │ │ ├── water6.png
│ │ │ ├── water7.png
│ │ │ ├── water8.png
│ │ │ ├── water9.png
│ │ │ └── __init__.py
│ │ ├── fighter
│ │ │ ├── fighter0.png
│ │ │ ├── fighter1.png
│ │ │ ├── fighter2.png
│ │ │ ├── fighter3.png
│ │ │ └── __init__.py
│ │ ├── priest
│ │ │ ├── priest0.png
│ │ │ ├── priest1.png
│ │ │ ├── priest2.png
│ │ │ ├── priest3.png
│ │ │ └── __init__.py
│ │ ├── headless
│ │ │ ├── headless0.png
│ │ │ ├── headless1.png
│ │ │ ├── headless2.png
│ │ │ ├── headless3.png
│ │ │ └── __init__.py
│ │ └── skeleton
│ │ │ ├── skeleton.png
│ │ │ ├── skeleton2.png
│ │ │ ├── skeleton3.png
│ │ │ ├── skeleton4.png
│ │ │ └── __init__.py
│ ├── __init__.py
│ └── imagecache.py
├── __init__.py
├── world
│ ├── __init__.py
│ ├── terrain
│ │ ├── __init__.py
│ │ ├── tile.py
│ │ ├── chunkmanager.py
│ │ ├── chunk.py
│ │ ├── mapcache.py
│ │ ├── perlin.py
│ │ ├── chunkgen.py
│ │ ├── terrain.py
│ │ └── simplex.py
│ └── world.py
├── player
│ ├── __init__.py
│ ├── entitycontrol.py
│ ├── abilitycontrol.py
│ ├── targetcontrol.py
│ └── player.py
├── abilities
│ ├── __init__.py
│ ├── projectiles
│ │ ├── __init__.py
│ │ ├── arrow.py
│ │ ├── rainbolt.py
│ │ └── star.py
│ ├── attack.py
│ ├── resurrection.py
│ ├── healbolt.py
│ ├── rainbowbolt.py
│ ├── poisonbolt.py
│ ├── bowshot.py
│ ├── ability.py
│ ├── magicmissile.py
│ ├── chainbolt.py
│ └── explosion.py
├── inventory
│ ├── __init__.py
│ └── inventory.py
├── sounds
│ ├── __init__.py
│ ├── default
│ │ ├── hit.wav
│ │ ├── pew.wav
│ │ ├── damage.wav
│ │ ├── oomph.wav
│ │ ├── step.wav
│ │ ├── fireball.wav
│ │ ├── explosion.wav
│ │ ├── step-brush.wav
│ │ ├── step-grass.wav
│ │ ├── step-sand.wav
│ │ ├── step-wood.wav
│ │ ├── magic-missile.wav
│ │ ├── resurrection.wav
│ │ ├── spell-healing.wav
│ │ ├── explosion-light.wav
│ │ └── fireball-impact.wav
│ └── soundcache.py
├── gui
│ ├── __init__.py
│ ├── cell.py
│ ├── status.py
│ ├── output.py
│ ├── displaypanel.py
│ ├── options.py
│ ├── gameview.py
│ ├── card.py
│ └── viewport.py
├── ai
│ ├── __init__.py
│ ├── task
│ │ ├── __init__.py
│ │ ├── cast.py
│ │ ├── wander.py
│ │ ├── fallback.py
│ │ ├── follow.py
│ │ ├── flee.py
│ │ └── pursue.py
│ └── aicontroller.py
├── entity
│ ├── __init__.py
│ ├── mobs
│ │ ├── orc.py
│ │ ├── rat.py
│ │ ├── spider.py
│ │ ├── headless.py
│ │ ├── snake.py
│ │ ├── fighter.py
│ │ ├── ettin.py
│ │ ├── priest.py
│ │ ├── bard.py
│ │ ├── mage.py
│ │ ├── __init__.py
│ │ ├── skeleton.py
│ │ └── entity.py
│ ├── mobspawner.py
│ ├── mobmanager.py
│ └── party.py
├── util
│ ├── __init__.py
│ ├── color.py
│ ├── enum.py
│ ├── line.py
│ ├── cardinals.py
│ └── vector.py
├── actions
│ ├── __init__.py
│ ├── quit.py
│ ├── look.py
│ ├── ready.py
│ ├── destroy.py
│ ├── build.py
│ └── cast.py
├── items
│ ├── item.py
│ ├── __init__.py
│ ├── tilestack.py
│ └── weapon.py
└── game.py
├── launch.py
├── README.md
├── .gitignore
└── LICENSE
/src/images/u5/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/__init__.py:
--------------------------------------------------------------------------------
1 | from game import Game
--------------------------------------------------------------------------------
/src/world/__init__.py:
--------------------------------------------------------------------------------
1 | from world import World
--------------------------------------------------------------------------------
/src/player/__init__.py:
--------------------------------------------------------------------------------
1 | from player import Player
2 |
--------------------------------------------------------------------------------
/src/abilities/__init__.py:
--------------------------------------------------------------------------------
1 | from ability import Ability
2 |
--------------------------------------------------------------------------------
/src/inventory/__init__.py:
--------------------------------------------------------------------------------
1 | from inventory import Inventory
--------------------------------------------------------------------------------
/src/sounds/__init__.py:
--------------------------------------------------------------------------------
1 | from soundcache import SoundCache
--------------------------------------------------------------------------------
/src/gui/__init__.py:
--------------------------------------------------------------------------------
1 |
2 |
3 | from gameview import Gameview
4 |
5 |
6 |
--------------------------------------------------------------------------------
/src/images/__init__.py:
--------------------------------------------------------------------------------
1 |
2 | from imagecache import ImageCache
3 |
4 |
--------------------------------------------------------------------------------
/launch.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/python
2 |
3 | from src import Game
4 |
5 | Game().run()
--------------------------------------------------------------------------------
/src/images/u5/grass/__init__.py:
--------------------------------------------------------------------------------
1 | def get(pos):
2 | return "grass.png"
3 |
4 |
--------------------------------------------------------------------------------
/src/ai/__init__.py:
--------------------------------------------------------------------------------
1 | from aicontroller import AIController
2 | import task
3 |
4 |
--------------------------------------------------------------------------------
/src/entity/__init__.py:
--------------------------------------------------------------------------------
1 | from mobmanager import MobManager
2 | from party import Party
3 |
--------------------------------------------------------------------------------
/src/images/u5/orc.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Greymerk/python-rpg/HEAD/src/images/u5/orc.png
--------------------------------------------------------------------------------
/src/images/u5/rat.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Greymerk/python-rpg/HEAD/src/images/u5/rat.png
--------------------------------------------------------------------------------
/src/images/u5/ankh.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Greymerk/python-rpg/HEAD/src/images/u5/ankh.png
--------------------------------------------------------------------------------
/src/images/u5/arrow.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Greymerk/python-rpg/HEAD/src/images/u5/arrow.png
--------------------------------------------------------------------------------
/src/images/u5/barrel.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Greymerk/python-rpg/HEAD/src/images/u5/barrel.png
--------------------------------------------------------------------------------
/src/images/u5/body.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Greymerk/python-rpg/HEAD/src/images/u5/body.png
--------------------------------------------------------------------------------
/src/images/u5/chest.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Greymerk/python-rpg/HEAD/src/images/u5/chest.png
--------------------------------------------------------------------------------
/src/images/u5/damage.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Greymerk/python-rpg/HEAD/src/images/u5/damage.png
--------------------------------------------------------------------------------
/src/images/u5/door.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Greymerk/python-rpg/HEAD/src/images/u5/door.png
--------------------------------------------------------------------------------
/src/images/u5/ettin.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Greymerk/python-rpg/HEAD/src/images/u5/ettin.png
--------------------------------------------------------------------------------
/src/images/u5/gore.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Greymerk/python-rpg/HEAD/src/images/u5/gore.png
--------------------------------------------------------------------------------
/src/images/u5/peaks.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Greymerk/python-rpg/HEAD/src/images/u5/peaks.png
--------------------------------------------------------------------------------
/src/images/u5/planks.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Greymerk/python-rpg/HEAD/src/images/u5/planks.png
--------------------------------------------------------------------------------
/src/images/u5/player.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Greymerk/python-rpg/HEAD/src/images/u5/player.png
--------------------------------------------------------------------------------
/src/images/u5/purse.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Greymerk/python-rpg/HEAD/src/images/u5/purse.png
--------------------------------------------------------------------------------
/src/images/u5/rocks.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Greymerk/python-rpg/HEAD/src/images/u5/rocks.png
--------------------------------------------------------------------------------
/src/images/u5/sand.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Greymerk/python-rpg/HEAD/src/images/u5/sand.png
--------------------------------------------------------------------------------
/src/images/u5/snake.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Greymerk/python-rpg/HEAD/src/images/u5/snake.png
--------------------------------------------------------------------------------
/src/images/u5/spider.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Greymerk/python-rpg/HEAD/src/images/u5/spider.png
--------------------------------------------------------------------------------
/src/images/u5/swamp.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Greymerk/python-rpg/HEAD/src/images/u5/swamp.png
--------------------------------------------------------------------------------
/src/images/u5/sword.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Greymerk/python-rpg/HEAD/src/images/u5/sword.png
--------------------------------------------------------------------------------
/src/images/u5/tile.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Greymerk/python-rpg/HEAD/src/images/u5/tile.png
--------------------------------------------------------------------------------
/src/images/u5/tree.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Greymerk/python-rpg/HEAD/src/images/u5/tree.png
--------------------------------------------------------------------------------
/src/images/u5/well.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Greymerk/python-rpg/HEAD/src/images/u5/well.png
--------------------------------------------------------------------------------
/src/images/u5/firebolt.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Greymerk/python-rpg/HEAD/src/images/u5/firebolt.png
--------------------------------------------------------------------------------
/src/images/u5/healbolt.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Greymerk/python-rpg/HEAD/src/images/u5/healbolt.png
--------------------------------------------------------------------------------
/src/images/u5/mountain.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Greymerk/python-rpg/HEAD/src/images/u5/mountain.png
--------------------------------------------------------------------------------
/src/images/u5/rainbolt.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Greymerk/python-rpg/HEAD/src/images/u5/rainbolt.png
--------------------------------------------------------------------------------
/src/images/u5/reticule.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Greymerk/python-rpg/HEAD/src/images/u5/reticule.png
--------------------------------------------------------------------------------
/src/images/u5/villager.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Greymerk/python-rpg/HEAD/src/images/u5/villager.png
--------------------------------------------------------------------------------
/src/sounds/default/hit.wav:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Greymerk/python-rpg/HEAD/src/sounds/default/hit.wav
--------------------------------------------------------------------------------
/src/sounds/default/pew.wav:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Greymerk/python-rpg/HEAD/src/sounds/default/pew.wav
--------------------------------------------------------------------------------
/src/images/u5/bard/bard0.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Greymerk/python-rpg/HEAD/src/images/u5/bard/bard0.png
--------------------------------------------------------------------------------
/src/images/u5/bard/bard1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Greymerk/python-rpg/HEAD/src/images/u5/bard/bard1.png
--------------------------------------------------------------------------------
/src/images/u5/bard/bard2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Greymerk/python-rpg/HEAD/src/images/u5/bard/bard2.png
--------------------------------------------------------------------------------
/src/images/u5/bard/bard3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Greymerk/python-rpg/HEAD/src/images/u5/bard/bard3.png
--------------------------------------------------------------------------------
/src/images/u5/brush/brush.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Greymerk/python-rpg/HEAD/src/images/u5/brush/brush.png
--------------------------------------------------------------------------------
/src/images/u5/floor-brick.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Greymerk/python-rpg/HEAD/src/images/u5/floor-brick.png
--------------------------------------------------------------------------------
/src/images/u5/foothills.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Greymerk/python-rpg/HEAD/src/images/u5/foothills.png
--------------------------------------------------------------------------------
/src/images/u5/grass/grass.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Greymerk/python-rpg/HEAD/src/images/u5/grass/grass.png
--------------------------------------------------------------------------------
/src/images/u5/mage/mage0.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Greymerk/python-rpg/HEAD/src/images/u5/mage/mage0.png
--------------------------------------------------------------------------------
/src/images/u5/mage/mage1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Greymerk/python-rpg/HEAD/src/images/u5/mage/mage1.png
--------------------------------------------------------------------------------
/src/images/u5/mage/mage2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Greymerk/python-rpg/HEAD/src/images/u5/mage/mage2.png
--------------------------------------------------------------------------------
/src/images/u5/mage/mage3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Greymerk/python-rpg/HEAD/src/images/u5/mage/mage3.png
--------------------------------------------------------------------------------
/src/images/u5/magicbolt.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Greymerk/python-rpg/HEAD/src/images/u5/magicbolt.png
--------------------------------------------------------------------------------
/src/images/u5/tree-dead.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Greymerk/python-rpg/HEAD/src/images/u5/tree-dead.png
--------------------------------------------------------------------------------
/src/images/u5/wall-brick.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Greymerk/python-rpg/HEAD/src/images/u5/wall-brick.png
--------------------------------------------------------------------------------
/src/images/u5/wall-stone.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Greymerk/python-rpg/HEAD/src/images/u5/wall-stone.png
--------------------------------------------------------------------------------
/src/sounds/default/damage.wav:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Greymerk/python-rpg/HEAD/src/sounds/default/damage.wav
--------------------------------------------------------------------------------
/src/sounds/default/oomph.wav:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Greymerk/python-rpg/HEAD/src/sounds/default/oomph.wav
--------------------------------------------------------------------------------
/src/sounds/default/step.wav:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Greymerk/python-rpg/HEAD/src/sounds/default/step.wav
--------------------------------------------------------------------------------
/src/images/u5/brush/forest.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Greymerk/python-rpg/HEAD/src/images/u5/brush/forest.png
--------------------------------------------------------------------------------
/src/images/u5/brush/jungle.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Greymerk/python-rpg/HEAD/src/images/u5/brush/jungle.png
--------------------------------------------------------------------------------
/src/images/u5/glazed-floor.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Greymerk/python-rpg/HEAD/src/images/u5/glazed-floor.png
--------------------------------------------------------------------------------
/src/images/u5/grass/grass2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Greymerk/python-rpg/HEAD/src/images/u5/grass/grass2.png
--------------------------------------------------------------------------------
/src/images/u5/water/water0.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Greymerk/python-rpg/HEAD/src/images/u5/water/water0.png
--------------------------------------------------------------------------------
/src/images/u5/water/water1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Greymerk/python-rpg/HEAD/src/images/u5/water/water1.png
--------------------------------------------------------------------------------
/src/images/u5/water/water10.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Greymerk/python-rpg/HEAD/src/images/u5/water/water10.png
--------------------------------------------------------------------------------
/src/images/u5/water/water11.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Greymerk/python-rpg/HEAD/src/images/u5/water/water11.png
--------------------------------------------------------------------------------
/src/images/u5/water/water12.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Greymerk/python-rpg/HEAD/src/images/u5/water/water12.png
--------------------------------------------------------------------------------
/src/images/u5/water/water13.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Greymerk/python-rpg/HEAD/src/images/u5/water/water13.png
--------------------------------------------------------------------------------
/src/images/u5/water/water14.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Greymerk/python-rpg/HEAD/src/images/u5/water/water14.png
--------------------------------------------------------------------------------
/src/images/u5/water/water15.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Greymerk/python-rpg/HEAD/src/images/u5/water/water15.png
--------------------------------------------------------------------------------
/src/images/u5/water/water2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Greymerk/python-rpg/HEAD/src/images/u5/water/water2.png
--------------------------------------------------------------------------------
/src/images/u5/water/water3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Greymerk/python-rpg/HEAD/src/images/u5/water/water3.png
--------------------------------------------------------------------------------
/src/images/u5/water/water4.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Greymerk/python-rpg/HEAD/src/images/u5/water/water4.png
--------------------------------------------------------------------------------
/src/images/u5/water/water5.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Greymerk/python-rpg/HEAD/src/images/u5/water/water5.png
--------------------------------------------------------------------------------
/src/images/u5/water/water6.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Greymerk/python-rpg/HEAD/src/images/u5/water/water6.png
--------------------------------------------------------------------------------
/src/images/u5/water/water7.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Greymerk/python-rpg/HEAD/src/images/u5/water/water7.png
--------------------------------------------------------------------------------
/src/images/u5/water/water8.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Greymerk/python-rpg/HEAD/src/images/u5/water/water8.png
--------------------------------------------------------------------------------
/src/images/u5/water/water9.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Greymerk/python-rpg/HEAD/src/images/u5/water/water9.png
--------------------------------------------------------------------------------
/src/sounds/default/fireball.wav:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Greymerk/python-rpg/HEAD/src/sounds/default/fireball.wav
--------------------------------------------------------------------------------
/src/images/u5/fighter/fighter0.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Greymerk/python-rpg/HEAD/src/images/u5/fighter/fighter0.png
--------------------------------------------------------------------------------
/src/images/u5/fighter/fighter1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Greymerk/python-rpg/HEAD/src/images/u5/fighter/fighter1.png
--------------------------------------------------------------------------------
/src/images/u5/fighter/fighter2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Greymerk/python-rpg/HEAD/src/images/u5/fighter/fighter2.png
--------------------------------------------------------------------------------
/src/images/u5/fighter/fighter3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Greymerk/python-rpg/HEAD/src/images/u5/fighter/fighter3.png
--------------------------------------------------------------------------------
/src/images/u5/priest/priest0.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Greymerk/python-rpg/HEAD/src/images/u5/priest/priest0.png
--------------------------------------------------------------------------------
/src/images/u5/priest/priest1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Greymerk/python-rpg/HEAD/src/images/u5/priest/priest1.png
--------------------------------------------------------------------------------
/src/images/u5/priest/priest2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Greymerk/python-rpg/HEAD/src/images/u5/priest/priest2.png
--------------------------------------------------------------------------------
/src/images/u5/priest/priest3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Greymerk/python-rpg/HEAD/src/images/u5/priest/priest3.png
--------------------------------------------------------------------------------
/src/sounds/default/explosion.wav:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Greymerk/python-rpg/HEAD/src/sounds/default/explosion.wav
--------------------------------------------------------------------------------
/src/sounds/default/step-brush.wav:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Greymerk/python-rpg/HEAD/src/sounds/default/step-brush.wav
--------------------------------------------------------------------------------
/src/sounds/default/step-grass.wav:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Greymerk/python-rpg/HEAD/src/sounds/default/step-grass.wav
--------------------------------------------------------------------------------
/src/sounds/default/step-sand.wav:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Greymerk/python-rpg/HEAD/src/sounds/default/step-sand.wav
--------------------------------------------------------------------------------
/src/sounds/default/step-wood.wav:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Greymerk/python-rpg/HEAD/src/sounds/default/step-wood.wav
--------------------------------------------------------------------------------
/src/images/u5/headless/headless0.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Greymerk/python-rpg/HEAD/src/images/u5/headless/headless0.png
--------------------------------------------------------------------------------
/src/images/u5/headless/headless1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Greymerk/python-rpg/HEAD/src/images/u5/headless/headless1.png
--------------------------------------------------------------------------------
/src/images/u5/headless/headless2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Greymerk/python-rpg/HEAD/src/images/u5/headless/headless2.png
--------------------------------------------------------------------------------
/src/images/u5/headless/headless3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Greymerk/python-rpg/HEAD/src/images/u5/headless/headless3.png
--------------------------------------------------------------------------------
/src/images/u5/skeleton/skeleton.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Greymerk/python-rpg/HEAD/src/images/u5/skeleton/skeleton.png
--------------------------------------------------------------------------------
/src/images/u5/skeleton/skeleton2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Greymerk/python-rpg/HEAD/src/images/u5/skeleton/skeleton2.png
--------------------------------------------------------------------------------
/src/images/u5/skeleton/skeleton3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Greymerk/python-rpg/HEAD/src/images/u5/skeleton/skeleton3.png
--------------------------------------------------------------------------------
/src/images/u5/skeleton/skeleton4.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Greymerk/python-rpg/HEAD/src/images/u5/skeleton/skeleton4.png
--------------------------------------------------------------------------------
/src/sounds/default/magic-missile.wav:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Greymerk/python-rpg/HEAD/src/sounds/default/magic-missile.wav
--------------------------------------------------------------------------------
/src/sounds/default/resurrection.wav:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Greymerk/python-rpg/HEAD/src/sounds/default/resurrection.wav
--------------------------------------------------------------------------------
/src/sounds/default/spell-healing.wav:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Greymerk/python-rpg/HEAD/src/sounds/default/spell-healing.wav
--------------------------------------------------------------------------------
/src/sounds/default/explosion-light.wav:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Greymerk/python-rpg/HEAD/src/sounds/default/explosion-light.wav
--------------------------------------------------------------------------------
/src/sounds/default/fireball-impact.wav:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Greymerk/python-rpg/HEAD/src/sounds/default/fireball-impact.wav
--------------------------------------------------------------------------------
/src/util/__init__.py:
--------------------------------------------------------------------------------
1 | from vector import Vector2
2 | from line import Line
3 | from cardinals import Cardinal
4 | from color import Color
5 |
--------------------------------------------------------------------------------
/src/ai/task/__init__.py:
--------------------------------------------------------------------------------
1 | from wander import Wander
2 | from follow import Follow
3 | from flee import Flee
4 | from pursue import Pursue
5 | from cast import Cast
6 | from fallback import Fallback
--------------------------------------------------------------------------------
/src/images/u5/water/__init__.py:
--------------------------------------------------------------------------------
1 | from time import time
2 |
3 | def get(pos):
4 | t = time() / 200 - int(time() / 200)
5 | t *= 600
6 | t = int(t)
7 | i = t % 15
8 | return 'water' + str(15 - i) + '.png'
9 |
10 |
--------------------------------------------------------------------------------
/src/abilities/projectiles/__init__.py:
--------------------------------------------------------------------------------
1 | from arrow import Arrow
2 | from star import Star
3 | from rainbolt import Rainbolt
4 |
5 | lookup = {}
6 | lookup["Star"] = Star
7 | lookup["Arrow"] = Arrow
8 | lookup["Rainbolt"] = Rainbolt
9 |
--------------------------------------------------------------------------------
/src/images/u5/brush/__init__.py:
--------------------------------------------------------------------------------
1 | def get(pos):
2 | images = []
3 | images.append("brush")
4 | images.append("forest")
5 | images.append("jungle")
6 | n = int(pos[0]) | int(pos[1])
7 | i = n % len(images)
8 | return images[i]+'.png'
9 |
10 |
--------------------------------------------------------------------------------
/src/util/color.py:
--------------------------------------------------------------------------------
1 | import time
2 | import colorsys
3 |
4 | class Color(object):
5 |
6 |
7 | @staticmethod
8 | def rainbow(r=None):
9 | if r is None:
10 | t = time.time()
11 | r = t - int(t)
12 | return colorsys.hls_to_rgb(r, 127, -1)
13 |
--------------------------------------------------------------------------------
/src/util/enum.py:
--------------------------------------------------------------------------------
1 | def enum(*sequential, **named):
2 | enums = dict(zip(sequential, range(len(sequential))), **named)
3 | reverse = dict((value, key) for key, value in enums.iteritems())
4 | enums['reverse_mapping'] = reverse
5 | return type('Enum', (), enums)
--------------------------------------------------------------------------------
/src/gui/cell.py:
--------------------------------------------------------------------------------
1 |
2 |
3 | class Cell(object):
4 |
5 | size = 32
6 |
7 | def __init__(self, pos, rel):
8 | self.pos = pos
9 | self.rel = rel
10 | self.observers = []
11 |
12 | def notify(self, event):
13 | for obs in self.observers:
14 | obs.notify(self, event)
15 |
16 |
--------------------------------------------------------------------------------
/src/images/u5/bard/__init__.py:
--------------------------------------------------------------------------------
1 | from time import time
2 | from random import Random
3 |
4 | def get(pos):
5 | p = int(pos[0]) | int(pos[1])
6 | t = time()
7 | rand = Random(int(t * 2) + p)
8 | n = rand.randint(0, 10)
9 | n += p
10 | return 'bard' + str(n % 4) + '.png'
11 |
12 |
--------------------------------------------------------------------------------
/src/images/u5/mage/__init__.py:
--------------------------------------------------------------------------------
1 | from time import time
2 | from random import Random
3 |
4 | def get(pos):
5 | p = int(pos[0]) | int(pos[1])
6 | t = time()
7 | rand = Random(int(t * 2) + p)
8 | n = rand.randint(0, 10)
9 | n += p
10 | return 'mage' + str(n % 4) + '.png'
11 |
12 |
--------------------------------------------------------------------------------
/src/images/u5/priest/__init__.py:
--------------------------------------------------------------------------------
1 | from time import time
2 | from random import Random
3 |
4 | def get(pos):
5 | p = int(pos[0]) | int(pos[1])
6 | t = time()
7 | rand = Random(int(t * 2) + p)
8 | n = rand.randint(0, 10)
9 | n += p
10 | return 'priest' + str(n % 4) + '.png'
11 |
12 |
--------------------------------------------------------------------------------
/src/images/u5/fighter/__init__.py:
--------------------------------------------------------------------------------
1 | from time import time
2 | from random import Random
3 |
4 | def get(pos):
5 | p = int(pos[0]) | int(pos[1])
6 | t = time()
7 | rand = Random(int(t * 2) + p)
8 | n = rand.randint(0, 10)
9 | n += p
10 | return 'fighter' + str(n % 4) + '.png'
11 |
12 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | python-rpg
2 | ==========
3 |
4 | A turn-based open world RPG based on AI controlled group mechanics
5 |
6 | -------------
7 | CONTROLS
8 | -------------
9 |
10 | Arrow Keys or WASD - move around
11 |
12 | - Q : quit
13 | - L : Look
14 | - B : Build (tiles)
15 | - G : Gather (tiles)
16 | - C : Cast / Attack
17 | - M : Map
18 |
19 |
--------------------------------------------------------------------------------
/src/actions/__init__.py:
--------------------------------------------------------------------------------
1 | from pygame import *
2 |
3 | from build import Build
4 | from quit import Quit
5 | from look import Look
6 | from destroy import Destroy
7 | from cast import Cast
8 | from ready import Ready
9 |
10 | lookup = {}
11 | lookup[K_b] = Build
12 | lookup[K_l] = Look
13 | lookup[K_g] = Destroy
14 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/src/items/item.py:
--------------------------------------------------------------------------------
1 | '''
2 | Created on 2013-06-05
3 |
4 | @author: brian
5 | '''
6 |
7 |
8 |
9 | class Item(object):
10 |
11 | def __init__(self):
12 | pass
13 |
14 | def load(self, data):
15 | pass
16 |
17 | def save(self):
18 | data = {}
19 | data['type'] = self.__class__.__name__
20 |
21 | return data
22 |
--------------------------------------------------------------------------------
/src/ai/task/cast.py:
--------------------------------------------------------------------------------
1 | '''
2 | Created on 2013-05-27
3 |
4 | @author: brian
5 | '''
6 |
7 | class Cast(object):
8 |
9 | def __init__(self, actor):
10 |
11 | self.actor = actor
12 |
13 | def condition(self):
14 |
15 | result = self.actor.getAction()
16 | if result is None:
17 | return False
18 |
19 | self.actor.action = result
20 | return True
21 |
22 | def do(self):
23 | pass
24 |
25 |
--------------------------------------------------------------------------------
/src/ai/task/wander.py:
--------------------------------------------------------------------------------
1 | '''
2 | Created on 2013-05-12
3 |
4 | @author: brian
5 | '''
6 |
7 | from src.util import Cardinal
8 |
9 | class Wander(object):
10 |
11 | def __init__(self, actor):
12 | self.actor = actor
13 |
14 | def condition(self):
15 | return True
16 |
17 | def do(self):
18 |
19 | direction = Cardinal.choice()
20 | self.actor.move(direction)
21 |
--------------------------------------------------------------------------------
/src/images/u5/headless/__init__.py:
--------------------------------------------------------------------------------
1 | from time import time
2 | from random import Random
3 |
4 | def get(pos):
5 | images = []
6 | images.append("headless0.png")
7 | images.append("headless1.png")
8 | images.append("headless2.png")
9 | images.append("headless3.png")
10 | p = int(pos[0]) | int(pos[1])
11 | t = time()
12 | rand = Random(int(t * 2) + p)
13 | n = rand.randint(0, 10)
14 | n += p
15 | i = n % len(images)
16 | return images[i]
17 |
18 |
--------------------------------------------------------------------------------
/src/images/u5/skeleton/__init__.py:
--------------------------------------------------------------------------------
1 | from time import time
2 | from random import Random
3 |
4 | def get(pos):
5 | images = []
6 | images.append("skeleton.png")
7 | images.append("skeleton2.png")
8 | images.append("skeleton3.png")
9 | images.append("skeleton4.png")
10 | p = int(pos[0]) | int(pos[1])
11 | t = time()
12 | rand = Random(int(t * 2) + p)
13 | n = rand.randint(0, 10)
14 | n += p
15 | i = n % len(images)
16 | return images[i]
17 |
18 |
--------------------------------------------------------------------------------
/src/gui/status.py:
--------------------------------------------------------------------------------
1 | import pygame
2 | from pygame.color import THECOLORS
3 | from src.util import Vector2
4 |
5 | class Status(object):
6 |
7 | def __init__(self, surface, pos, player, images):
8 | self.pos = pos
9 | self.surface = surface
10 | self.player = player
11 | self.images = images
12 | self.font = pygame.font.Font(None,16)
13 | self.size = 32
14 |
15 | def draw(self):
16 | self.surface.fill(THECOLORS["black"])
17 |
18 | def notify(self, pos, event):
19 | pass
20 |
--------------------------------------------------------------------------------
/src/items/__init__.py:
--------------------------------------------------------------------------------
1 |
2 | from weapon import Weapon
3 | from tilestack import TileStack
4 |
5 | class ItemFactory(object):
6 |
7 | itemList = {}
8 | itemList["Weapon"] = Weapon
9 | itemList["TileStack"] = TileStack
10 |
11 | def __init__(self, terrain):
12 | self.weapons = Weapon
13 | self.terrain = terrain
14 |
15 | def load(self, data):
16 | itemType = data['type']
17 |
18 | o = self.itemList[itemType]()
19 | o.load(data)
20 | return o
21 |
22 | def getStack(self, material, size=1):
23 | return TileStack(material, size)
--------------------------------------------------------------------------------
/src/sounds/soundcache.py:
--------------------------------------------------------------------------------
1 |
2 | import os
3 | import pygame
4 |
5 | class SoundCache(object):
6 |
7 | def __init__(self, soundDir="default"):
8 |
9 | self.basePath = os.path.dirname(__file__)
10 | self.soundDir = soundDir
11 | self.cache = {}
12 |
13 | def get(self, name):
14 |
15 | if name in self.cache:
16 | return self.cache[name]
17 | else:
18 | path = os.path.join(self.basePath, self.soundDir, name)
19 | self.cache[name] = pygame.mixer.Sound(path)
20 | self.cache[name].set_volume(0.5)
21 | return self.cache[name]
22 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | save
2 |
3 | *.py[cod~]
4 |
5 | # C extensions
6 | *.so
7 |
8 | # Packages
9 | *.egg
10 | *.egg-info
11 | dist
12 | build
13 | eggs
14 | parts
15 | bin
16 | var
17 | sdist
18 | develop-eggs
19 | .installed.cfg
20 | lib
21 | lib64
22 | __pycache__
23 |
24 | # Installer logs
25 | pip-log.txt
26 |
27 | # Unit test / coverage reports
28 | .coverage
29 | .tox
30 | nosetests.xml
31 |
32 | # Translations
33 | *.mo
34 |
35 | # Mr Developer
36 | .mr.developer.cfg
37 | .project
38 | .pydevproject
39 |
40 | /*.jpg
41 | /*.png
42 |
43 |
--------------------------------------------------------------------------------
/src/world/terrain/__init__.py:
--------------------------------------------------------------------------------
1 | from chunkmanager import ChunkManager
2 |
3 | from terrain import Void
4 | from terrain import Grass
5 | from terrain import Brush
6 | from terrain import FloorBrick
7 | from terrain import Foothills
8 | from terrain import WallBrick
9 | from terrain import Mountain
10 | from terrain import Peak
11 | from terrain import Water
12 | from terrain import WallStone
13 | from terrain import Tree
14 | from terrain import Well
15 | from terrain import Door
16 | from terrain import Rock
17 | from terrain import DeadTree
18 | from terrain import Sand
19 |
20 | from terrain import lookup
21 |
--------------------------------------------------------------------------------
/src/items/tilestack.py:
--------------------------------------------------------------------------------
1 |
2 | from item import Item
3 |
4 | class TileStack(Item):
5 |
6 | def __init__(self, id=0, size=1):
7 | self.id = id
8 | self.size = size
9 |
10 | def add(self, amount=1):
11 | self.size += amount
12 |
13 | def getmaterial(self):
14 | return self.id
15 |
16 | def save(self):
17 | data = {}
18 | data['type'] = self.__class__.__name__
19 | data['material'] = self.id
20 | data['size'] = self.size
21 | return data
22 |
23 | def load(self, data):
24 | if 'material' in data:
25 | self.id = data['material']
26 | if 'size' in data:
27 | self.size = data['size']
28 |
--------------------------------------------------------------------------------
/src/entity/mobs/orc.py:
--------------------------------------------------------------------------------
1 | '''
2 | Created on 2013-05-12
3 |
4 | @author: brian
5 | '''
6 |
7 | import pygame
8 | from entity import Entity
9 | from src.ai import task
10 |
11 | class Orc(Entity):
12 |
13 | living = "orc"
14 | dead = "gore"
15 |
16 |
17 | def __init__(self, world):
18 | Entity.__init__(self, world)
19 | self.world = world
20 | self.health = self.maxHealth = 20
21 |
22 | self.ai.addAI(task.Flee(self))
23 | self.ai.addAI(task.Cast(self))
24 | self.ai.addAI(task.Pursue(self))
25 | self.ai.addAI(task.Wander(self))
26 |
27 | self.singular = 'an orc'
28 |
29 | def equip(self):
30 | pass
31 |
--------------------------------------------------------------------------------
/src/ai/aicontroller.py:
--------------------------------------------------------------------------------
1 | '''
2 | Created on 2013-05-12
3 |
4 | @author: brian
5 | '''
6 |
7 | class AIController(object):
8 |
9 | def __init__(self):
10 | self.aiList = []
11 | self.active = True
12 |
13 | def act(self):
14 |
15 | if not self.active:
16 | return
17 |
18 | for ai in self.aiList:
19 |
20 | if(ai.condition()):
21 | ai.do()
22 | return
23 |
24 | def addAI(self, ai):
25 | self.aiList.append(ai)
26 |
27 | def save(self):
28 |
29 | data = {}
30 | data['active'] = self.active
31 |
32 | return data
33 |
34 | def load(self, data):
35 |
36 | self.active = data['active']
37 |
--------------------------------------------------------------------------------
/src/entity/mobs/rat.py:
--------------------------------------------------------------------------------
1 | '''
2 | Created on 2013-05-16
3 |
4 | @author: brian
5 | '''
6 |
7 | import pygame
8 | from entity import Entity
9 | from src.ai import task
10 |
11 | class Rat(Entity):
12 |
13 | living = "rat"
14 | dead = "gore"
15 |
16 |
17 | def __init__(self, world):
18 | Entity.__init__(self, world)
19 | self.world = world
20 | self.health = self.maxHealth = 15
21 |
22 |
23 | self.ai.addAI(task.Flee(self))
24 | self.ai.addAI(task.Cast(self))
25 | self.ai.addAI(task.Pursue(self))
26 | self.ai.addAI(task.Wander(self))
27 | self.hostile = True
28 |
29 | self.singular = 'a rat'
30 |
31 | def equip(self):
32 | pass
33 |
--------------------------------------------------------------------------------
/src/gui/output.py:
--------------------------------------------------------------------------------
1 | import pygame
2 | from src.util import Vector2
3 |
4 | class Output(object):
5 |
6 | def __init__(self, surface, pos, inBuffer):
7 | self.pos = pos
8 | self.surface = surface
9 | self.buffer = inBuffer
10 | self.fontobject = pygame.font.Font(None,24)
11 |
12 | def draw(self):
13 | self.surface.fill((0,0,0))
14 |
15 | count = 0
16 | for line in self.buffer[-16:]:
17 | self.surface.blit(self.fontobject.render('>> ' + line, 1, (255,255,255)), (0, 16*count))
18 | count += 1
19 |
20 | def append(self, message):
21 | self.buffer.append(message)
22 |
23 | def notify(self, pos, event):
24 | pass
25 |
26 |
27 |
--------------------------------------------------------------------------------
/src/entity/mobs/spider.py:
--------------------------------------------------------------------------------
1 | '''
2 | Created on 2013-05-16
3 |
4 | @author: brian
5 | '''
6 |
7 | import pygame
8 | from entity import Entity
9 |
10 | from src.ai import task
11 |
12 | class Spider(Entity):
13 |
14 | living = "spider"
15 | dead = "gore"
16 |
17 |
18 | def __init__(self, world):
19 | Entity.__init__(self, world)
20 |
21 | self.world = world
22 | self.hostile = True
23 | self.health = self.maxHealth = 15
24 |
25 | self.ai.addAI(task.Flee(self))
26 | self.ai.addAI(task.Cast(self))
27 | self.ai.addAI(task.Pursue(self))
28 | self.ai.addAI(task.Wander(self))
29 | self.singular = 'a spider'
30 |
31 | def equip(self):
32 | pass
33 |
--------------------------------------------------------------------------------
/src/player/entitycontrol.py:
--------------------------------------------------------------------------------
1 | import pygame
2 | from src.util import Vector2
3 |
4 | class EntityControl(object):
5 |
6 | def __init__(self, player):
7 | self.player = player
8 |
9 | def notify(self, entity, event):
10 | if event.type == pygame.MOUSEBUTTONUP and event.button == 1:
11 | if self.player.action is not None:
12 | self.player.target = entity.position
13 | return
14 |
15 | if entity in self.player.party:
16 | self.player.setLeader(entity)
17 |
18 | if event.type == pygame.MOUSEMOTION:
19 | if self.player.action is not None:
20 | rel = Vector2(entity.position)
21 | rel -= self.player.avatar.position
22 | self.player.reticle = rel
23 |
24 |
--------------------------------------------------------------------------------
/src/entity/mobs/headless.py:
--------------------------------------------------------------------------------
1 | '''
2 | Created on 2013-05-16
3 |
4 | @author: brian
5 | '''
6 |
7 | import pygame
8 | from entity import Entity
9 | from src.ai import task
10 | from src.abilities import *
11 |
12 | class Headless(Entity):
13 |
14 | living = "headless"
15 | dead = "gore"
16 |
17 |
18 | def __init__(self, world):
19 | Entity.__init__(self, world)
20 | self.health = self.maxHealth = 20
21 |
22 |
23 | self.ai.addAI(task.Flee(self))
24 | self.ai.addAI(task.Cast(self))
25 | self.ai.addAI(task.Pursue(self))
26 | self.ai.addAI(task.Follow(self))
27 | self.ai.addAI(task.Wander(self))
28 | self.hostile = True
29 |
30 | self.singular = 'a headless'
31 |
32 | def equip(self):
33 | pass
34 |
--------------------------------------------------------------------------------
/src/player/abilitycontrol.py:
--------------------------------------------------------------------------------
1 | import pygame
2 | from src.util import Vector2
3 | from src.actions import Cast
4 |
5 | # observer of mouse events on viewport spaces
6 | class AbilityControl(object):
7 |
8 | def __init__(self, player):
9 | self.player = player
10 |
11 | def notify(self, ability, event):
12 | if event.type == pygame.MOUSEBUTTONUP and event.button == 3:
13 | ability.caster.quickcast = ability
14 | return
15 |
16 | if event.type == pygame.MOUSEBUTTONUP and event.button == 1:
17 | if self.player.action is None:
18 | self.player.setLeader(ability.caster)
19 | if not ability.ready():
20 | self.player.log.append('Ability on cooldown!')
21 | return
22 | self.player.action = Cast(self.player, ability)
23 |
--------------------------------------------------------------------------------
/src/entity/mobs/snake.py:
--------------------------------------------------------------------------------
1 | '''
2 | Created on 2013-05-16
3 |
4 | @author: brian
5 | '''
6 |
7 | import pygame
8 | from entity import Entity
9 |
10 | from src.ai import task
11 | from src.abilities import *
12 |
13 | class Snake(Entity):
14 |
15 | living = "snake"
16 | dead = "gore"
17 |
18 |
19 | def __init__(self, world):
20 | Entity.__init__(self, world)
21 |
22 | self.world = world
23 | self.hostile = True
24 | self.health = self.maxHealth = 15
25 |
26 | self.ai.addAI(task.Flee(self))
27 | self.ai.addAI(task.Cast(self))
28 | self.ai.addAI(task.Pursue(self))
29 | self.ai.addAI(task.Wander(self))
30 | self.singular = 'a snake'
31 |
32 | def equip(self):
33 | self.abilities = [Ability(self, Ability.lookup["PoisonBolt"])]
34 |
--------------------------------------------------------------------------------
/src/entity/mobs/fighter.py:
--------------------------------------------------------------------------------
1 | '''
2 | Created on 2013-05-26
3 |
4 | @author: brian
5 | '''
6 | from entity import Entity
7 | import pygame
8 | from random import randint
9 | from random import choice
10 | from src.ai import task
11 |
12 | class Fighter(Entity):
13 |
14 | living = "fighter"
15 | dead = "body"
16 |
17 | def __init__(self, world):
18 | Entity.__init__(self, world)
19 | self.singular = 'a fighter'
20 |
21 |
22 | self.ai.addAI(task.Flee(self))
23 | self.ai.addAI(task.Cast(self))
24 | self.ai.addAI(task.Pursue(self))
25 | self.ai.addAI(task.Follow(self))
26 | self.ai.addAI(task.Wander(self))
27 |
28 | def equip(self):
29 | pass
30 |
31 | @classmethod
32 | def onDamage(cls, sounds):
33 | sounds.get("damage.wav").play()
34 |
35 |
--------------------------------------------------------------------------------
/src/items/weapon.py:
--------------------------------------------------------------------------------
1 | '''
2 | Created on 2013-06-04
3 |
4 | @author: brian
5 | '''
6 |
7 | from random import randint
8 | from random import choice
9 |
10 | from pygame.color import THECOLORS
11 |
12 | from item import Item
13 | from src.abilities import *
14 |
15 | class Weapon(Item):
16 |
17 | def __init__(self, name="None", damage=(1, 2), range=1):
18 | self.name = name
19 | self.damage = damage
20 | self.range = range
21 |
22 | def save(self):
23 | data = {}
24 | data["type"] = self.__class__.__name__
25 | data["name"] = self.name
26 | data["damage"] = self.damage
27 | data["range"] = self.range
28 | return data
29 |
30 | def load(self, data):
31 | self.name = data["name"]
32 | self.damage = data["damage"]
33 | self.range = data["range"]
34 |
35 |
--------------------------------------------------------------------------------
/src/entity/mobs/ettin.py:
--------------------------------------------------------------------------------
1 | '''
2 | Created on 2013-05-16
3 |
4 | @author: brian
5 | '''
6 | from random import choice
7 | import pygame
8 |
9 | from entity import Entity
10 | from src.ai import task
11 | from src.abilities import Ability
12 |
13 | class Ettin(Entity):
14 |
15 | living = "ettin"
16 | dead = "gore"
17 |
18 | def __init__(self, world):
19 | Entity.__init__(self, world)
20 | self.world = world
21 | self.hostile = True
22 |
23 | self.health = self.maxHealth = 40
24 |
25 | self.ai.addAI(task.Flee(self))
26 | self.ai.addAI(task.Fallback(self))
27 | self.ai.addAI(task.Cast(self))
28 | self.ai.addAI(task.Pursue(self))
29 | self.ai.addAI(task.Wander(self))
30 |
31 | self.singular = 'an ettin'
32 |
33 | def equip(self):
34 | self.abilities = [Ability(self, Ability.lookup["MagicMissile"])]
35 |
--------------------------------------------------------------------------------
/src/entity/mobs/priest.py:
--------------------------------------------------------------------------------
1 | '''
2 | Created on 2013-05-27
3 |
4 | @author: brian
5 | '''
6 |
7 | from entity import Entity
8 | import pygame
9 | from random import randint
10 | from src.ai import task
11 | from src.abilities import Ability
12 |
13 | class Priest(Entity):
14 |
15 | living = "priest"
16 | dead = "body"
17 |
18 | def __init__(self, world):
19 | Entity.__init__(self, world)
20 | self.singular = 'a priest'
21 |
22 | self.ai.addAI(task.Fallback(self))
23 | self.ai.addAI(task.Cast(self))
24 | self.ai.addAI(task.Follow(self))
25 | self.ai.addAI(task.Wander(self))
26 |
27 | def equip(self):
28 | self.abilities = [Ability(self, Ability.lookup["HealBolt"]), Ability(self, Ability.lookup["Resurrection"])]
29 |
30 | @classmethod
31 | def onDamage(cls, sounds):
32 | sounds.get("damage.wav").play()
33 |
--------------------------------------------------------------------------------
/src/entity/mobs/bard.py:
--------------------------------------------------------------------------------
1 | '''
2 | Created on 2013-05-28
3 |
4 | @author: brian
5 | '''
6 | from entity import Entity
7 | import pygame
8 | from src.ai import task
9 | from src.abilities import Ability
10 |
11 | class Bard(Entity):
12 |
13 | living = "bard"
14 | dead = "body"
15 |
16 | def __init__(self, world):
17 | Entity.__init__(self, world)
18 | self.singular = 'a bard'
19 |
20 | self.ai.addAI(task.Flee(self))
21 | self.ai.addAI(task.Cast(self))
22 | self.ai.addAI(task.Pursue(self))
23 | self.ai.addAI(task.Follow(self))
24 | self.ai.addAI(task.Wander(self))
25 | self.sight = 7
26 |
27 | def equip(self):
28 | self.abilities = [Ability(self, Ability.lookup["BowShot"]), Ability(self, Ability.lookup["HealBolt"])]
29 |
30 | @classmethod
31 | def onDamage(cls, sounds):
32 | sounds.get("damage.wav").play()
33 |
--------------------------------------------------------------------------------
/src/entity/mobs/mage.py:
--------------------------------------------------------------------------------
1 | '''
2 | Created on 2013-05-27
3 |
4 | @author: brian
5 | '''
6 |
7 | from entity import Entity
8 | import pygame
9 | from random import randint
10 | from src.ai import task
11 | from src.abilities import *
12 |
13 | class Mage(Entity):
14 |
15 | living = "mage"
16 | dead = "body"
17 |
18 | def __init__(self, world):
19 | Entity.__init__(self, world)
20 | self.singular = 'a mage'
21 |
22 | self.ai.addAI(task.Fallback(self))
23 | self.ai.addAI(task.Cast(self))
24 | self.ai.addAI(task.Follow(self))
25 | self.ai.addAI(task.Wander(self))
26 |
27 | def equip(self):
28 | self.abilities = [Ability(self, Ability.lookup["Explosion"]), Ability(self, Ability.lookup["ChainBolt"]), Ability(self, Ability.lookup["RainbowBolt"])]
29 |
30 | @classmethod
31 | def onDamage(cls, sounds):
32 | sounds.get("damage.wav").play()
33 |
--------------------------------------------------------------------------------
/src/util/line.py:
--------------------------------------------------------------------------------
1 | from vector import Vector2
2 |
3 | class Line(object):
4 |
5 | def __init__(self, start, end):
6 | self.start = start
7 | self.end = end
8 |
9 |
10 | def __iter__(self):
11 | error = 0
12 | deltax = self.end.x - self.start.x
13 | deltay = self.end.y - self.start.y
14 | if deltax == 0:
15 | for y in xrange(int(self.start.y), int(self.end.y), Line.sign(self.end.y - self.start.y)):
16 | yield Vector2(self.start.x, y)
17 |
18 | else:
19 | deltaerr = abs(deltay / deltax)
20 | y = self.start.y
21 | for x in xrange(int(self.start.x), int(self.end.x), Line.sign(self.end.x - self.start.x)):
22 | yield Vector2(x, y)
23 | error += deltaerr
24 | while error >= 0.5:
25 | yield Vector2(x, y)
26 | y += Line.sign(self.end.y - self.start.y)
27 | error -= 1.0
28 |
29 | @staticmethod
30 | def sign(n):
31 | return 1 if n >= 0 else -1
32 |
33 |
--------------------------------------------------------------------------------
/src/actions/quit.py:
--------------------------------------------------------------------------------
1 | '''
2 | Created on 2013-05-02
3 |
4 | @author: brian
5 | '''
6 | import pygame
7 | from pygame.locals import *
8 |
9 |
10 | class Quit:
11 |
12 | def __init__(self, player):
13 | self.player = player
14 | self.quit = None
15 | self.finished = False
16 | self.player.log.append('Quit? (Y/N)')
17 |
18 |
19 | def nextStep(self):
20 |
21 | if(self.finished):
22 | if(self.quit):
23 | self.player.world.quit()
24 | else:
25 | return True
26 |
27 |
28 | e = pygame.event.poll()
29 |
30 | if(e is None):
31 | return False
32 |
33 | if e.type == KEYDOWN:
34 |
35 | if(e.key == K_y):
36 | self.quit = True
37 | self.finished = True
38 | return False
39 | elif(e.key == K_n):
40 | self.player.log.append('Pass')
41 | return True
--------------------------------------------------------------------------------
/src/entity/mobs/__init__.py:
--------------------------------------------------------------------------------
1 | from orc import Orc
2 | from rat import Rat
3 | from snake import Snake
4 | from spider import Spider
5 | from headless import Headless
6 | from ettin import Ettin
7 | from fighter import Fighter
8 | from mage import Mage
9 | from bard import Bard
10 | from priest import Priest
11 | from skeleton import *
12 |
13 | lookup = {}
14 | lookup['Orc'] = Orc
15 | lookup['Rat'] = Rat
16 | lookup['Snake'] = Snake
17 | lookup['Spider'] = Spider
18 | lookup['Headless'] = Headless
19 | lookup['Ettin'] = Ettin
20 | lookup['Fighter'] = Fighter
21 | lookup['Mage'] = Mage
22 | lookup['Bard'] = Bard
23 | lookup['Priest'] = Priest
24 | lookup['SkeletalWarrior'] = SkeletalWarrior
25 | lookup['SkeletalMage'] = SkeletalMage
26 | lookup['SkeletalArcher'] = SkeletalArcher
27 |
28 | hostiles = []
29 | hostiles.append(Orc)
30 | hostiles.append(Rat)
31 | hostiles.append(Snake)
32 | hostiles.append(Spider)
33 | hostiles.append(Headless)
34 | hostiles.append(Ettin)
35 | hostiles.append(SkeletalWarrior)
36 | hostiles.append(SkeletalMage)
37 | hostiles.append(SkeletalArcher)
38 |
--------------------------------------------------------------------------------
/src/actions/look.py:
--------------------------------------------------------------------------------
1 | '''
2 | Created on 2013-05-02
3 |
4 | @author: brian
5 | '''
6 |
7 | import pygame
8 | from pygame.locals import *
9 | from src.util import Cardinal
10 |
11 | class Look:
12 |
13 | def __init__(self, player):
14 | self.player = player
15 | self.finished = False
16 | self.player.log.append('Look... (Choose a direction)')
17 |
18 | def nextStep(self):
19 |
20 | e = pygame.event.poll()
21 |
22 | if e is None:
23 | return False
24 |
25 | if e.type != KEYDOWN:
26 | return False
27 |
28 | if(e.key in Cardinal.key_map.keys()):
29 |
30 | direction = Cardinal.values[Cardinal.key_map[e.key]]
31 | x = self.player.party.getLeader().position[0] + direction[0]
32 | y = self.player.party.getLeader().position[1] + direction[1]
33 | pos = (x, y)
34 | result = self.player.world.look(pos)
35 | self.player.log.append('Thou dost see ' + result)
36 | return True
37 | elif(e.key == K_ESCAPE):
38 | self.player.log.append('Pass')
39 | return True
40 |
41 | return False
42 |
--------------------------------------------------------------------------------
/src/world/terrain/tile.py:
--------------------------------------------------------------------------------
1 | import terrain
2 |
3 | class Tile:
4 |
5 | def __init__(self):
6 | self.layers = []
7 |
8 | def getGround(self):
9 | tileId = self.layers[-1:][0]
10 | return terrain.lookup[tileId]
11 |
12 | def setGround(self, id):
13 | if(len(self.layers) > 0):
14 | self.layers = []
15 |
16 | self.layers.append(id)
17 |
18 | def isPassable(self):
19 | return self.getGround().passable
20 |
21 | def isTransparent(self):
22 | return self.getGround().transparent
23 |
24 | def isBreakable(self):
25 | return self.getGround().breakable
26 |
27 | def getName(self):
28 | return self.getGround().singular
29 |
30 | def canBuild(self):
31 | top = self.layers[-1:][0]
32 | return terrain.lookup[top].passable
33 |
34 | def build(self, id):
35 | self.layers.append(id)
36 |
37 | def destroy(self):
38 | if(self.isBreakable()):
39 | id = self.layers.pop()
40 | if(len(self.layers) == 0):
41 | self.layers.append(terrain.Grass.id)
42 | return terrain.lookup[id].drop()
43 |
44 | def save(self):
45 | data = {}
46 | data['layers'] = self.layers
47 | return data
48 |
49 | def load(self, data):
50 | if 'layers' in data.keys():
51 | self.layers = data['layers']
52 |
53 |
--------------------------------------------------------------------------------
/src/abilities/attack.py:
--------------------------------------------------------------------------------
1 | '''
2 | Created on 2013-06-01
3 |
4 | @author: brian
5 | '''
6 |
7 | from random import randint
8 |
9 | class Attack(object):
10 |
11 | range = 1
12 | damage = 2, 5
13 | heal = False
14 | name = "Attack"
15 | icon = "sword"
16 | cooldown = 0
17 |
18 | def __init__(self, caster, location, item):
19 |
20 | self.caster = caster
21 | self.target = location
22 | self.item = item
23 | self.range = self.__class__.range
24 | self.damage = self.__class__.damage
25 |
26 | casterName = self.caster.getName()
27 | self.entityHit = self.caster.world.getEntityFromLocation(self.target)
28 | if not self.entityHit is None:
29 | self.entityHit.inflict(self.caster, randint(self.damage[0], self.damage[1]))
30 | else:
31 | self.caster.world.log.append(casterName + ' attacked nothing!')
32 |
33 | self.done = False
34 |
35 | def update(self):
36 | return True
37 |
38 | def draw(self, surface, position, visible):
39 | pass
40 |
41 | @classmethod
42 | def validTarget(cls, actor, target):
43 |
44 | if actor is target:
45 | return False
46 |
47 | if not target.isAlive():
48 | return False
49 |
50 | if target in actor.getFriends():
51 | return False
52 |
53 | return True
54 |
--------------------------------------------------------------------------------
/src/actions/ready.py:
--------------------------------------------------------------------------------
1 | '''
2 | Created on 2013-06-04
3 |
4 | @author: brian
5 | '''
6 |
7 | import pygame
8 | from pygame.locals import *
9 |
10 |
11 | class Ready(object):
12 |
13 | def __init__(self, player):
14 | self.player = player
15 | self.player.log.append('Ready a new weapon')
16 | self.choice = None
17 |
18 | def nextStep(self):
19 |
20 | if(self.choice is not None):
21 | self.player.party.getLeader().inventory.weapon = self.choices[self.choice]()
22 | self.player.log.append('Equipped a ' + self.player.party.getLeader().inventory.weapon.__class__.__name__)
23 | return True
24 |
25 | e = pygame.event.poll()
26 |
27 | if e is None:
28 | return False
29 |
30 | if e.type != KEYDOWN:
31 | return False
32 |
33 | if(self.choices.has_key(e.key)):
34 | self.choice = e.key
35 |
36 | return False
37 |
38 | elif(e.key == K_ESCAPE):
39 | self.player.log.append('Cancelled')
40 | return True
41 |
42 | return False
43 |
44 |
--------------------------------------------------------------------------------
/src/images/imagecache.py:
--------------------------------------------------------------------------------
1 |
2 | import os
3 | import pygame
4 | import imp
5 | import importlib
6 |
7 | class ImageCache(object):
8 |
9 | def __init__(self, textureDir="u5"):
10 |
11 | self.basePath = os.path.dirname(__file__)
12 | self.textureDir = textureDir
13 | self.cache = {}
14 | self.animations = {}
15 |
16 | def get(self, name, pos = None):
17 | anim = self.getAnimation(name)
18 |
19 | if anim is None:
20 | return self.getImage(name + '.png')
21 |
22 | return self.getImage(name + '/' + anim.get(pos))
23 |
24 | def getAnimation(self, name):
25 | if not name in self.animations:
26 | path = os.path.join(self.basePath, self.textureDir, name)
27 | if os.path.isdir(path):
28 | pkg = 'src.images.' + self.textureDir + '.' + name
29 | anim = importlib.import_module(pkg)
30 | self.animations[name] = anim
31 | else:
32 | self.animations[name] = None
33 | return self.animations[name]
34 |
35 | def getImage(self, name):
36 | if name in self.cache:
37 | return self.cache[name]
38 | else:
39 | return self.addImage(name)
40 |
41 | def addImage(self, name):
42 | path = os.path.join(self.basePath, self.textureDir, name)
43 | self.cache[name] = pygame.image.load(path).convert_alpha()
44 | return self.cache[name]
45 |
--------------------------------------------------------------------------------
/src/ai/task/fallback.py:
--------------------------------------------------------------------------------
1 | '''
2 | Created on 2013-05-29
3 |
4 | @author: brian
5 | '''
6 |
7 | from src.util import Vector2
8 |
9 | class Fallback(object):
10 |
11 | def __init__(self, actor):
12 | self.actor = actor
13 | self.target = None
14 |
15 | def condition(self):
16 |
17 | self.target = None
18 |
19 | for entity in self.actor.getEnemies():
20 | if self.actor.distance(entity.position) < 3:
21 | self.target = entity
22 |
23 | if self.target is None:
24 | return False
25 |
26 | pos = Vector2(self.actor.position)
27 | pos -= self.target.position
28 |
29 | x, y = pos
30 |
31 | direction = (self.sign(x), 0) if abs(x) > abs(y) else (0, self.sign(y))
32 | alternative = (self.sign(x), 0) if abs(x) <= abs(y) else (0, self.sign(y))
33 |
34 | pos = Vector2(self.actor.position)
35 | pos += direction
36 |
37 | pos2 = Vector2(self.actor.position)
38 | pos2 += alternative
39 |
40 | if self.actor.world.isLocationPassable(pos):
41 | self.direction = direction
42 | return True
43 |
44 | elif self.actor.world.isLocationPassable(pos2):
45 | self.direction = alternative
46 | return True
47 |
48 | return False
49 |
50 |
51 |
52 | def do(self):
53 | self.actor.move(self.direction)
54 |
55 | @staticmethod
56 | def sign(n):
57 | return -1 if n < 0 else 1
58 |
59 |
--------------------------------------------------------------------------------
/src/actions/destroy.py:
--------------------------------------------------------------------------------
1 | '''
2 | Created on 2013-05-02
3 |
4 | @author: brian
5 | '''
6 |
7 | import pygame
8 | from pygame.locals import *
9 | from src.util import Cardinal
10 |
11 | class Destroy:
12 |
13 | def __init__(self, player):
14 | self.player = player
15 | self.world = player.world
16 | self.direction = None
17 | self.finished = False
18 | self.player.log.append('Gather... Pick a direction')
19 |
20 | def nextStep(self):
21 |
22 | if(self.direction is not None):
23 | x = self.player.party.getLeader().position[0] + self.direction[0]
24 | y = self.player.party.getLeader().position[1] + self.direction[1]
25 |
26 | tile = self.world.getTile((x, y)).getName()
27 |
28 | if(self.world.getTile((x, y)).isBreakable()):
29 | material = self.world.destroy((x, y))
30 | self.player.avatar.pocket(material)
31 | self.player.log.append('Successfully gethered ' + tile)
32 | else:
33 | self.player.log.append('Unable to gether ' + tile)
34 |
35 | return True
36 |
37 | e = pygame.event.poll()
38 |
39 | if e is None:
40 | return False
41 |
42 | if e.type != KEYDOWN:
43 | return False
44 |
45 | if(e.key in Cardinal.key_map.keys()):
46 | self.direction = Cardinal.values[Cardinal.key_map[e.key]]
47 | return False
48 | elif(e.key == K_ESCAPE):
49 | self.player.log.append('Cancelled')
50 | return True
51 |
52 | return False
53 |
--------------------------------------------------------------------------------
/src/util/cardinals.py:
--------------------------------------------------------------------------------
1 |
2 | from pygame.locals import *
3 | from vector import Vector2
4 | from enum import enum
5 | from random import choice
6 |
7 | class Cardinal(object):
8 |
9 | directions = enum('NORTH', 'EAST', 'WEST', 'SOUTH')
10 |
11 | key_map = {}
12 | key_map[K_UP] = directions.NORTH
13 | key_map[K_DOWN] = directions.SOUTH
14 | key_map[K_LEFT] = directions.WEST
15 | key_map[K_RIGHT] = directions.EAST
16 |
17 | names = {}
18 | names[directions.NORTH] = "North"
19 | names[directions.SOUTH] = "South"
20 | names[directions.WEST] = "West"
21 | names[directions.EAST] = "East"
22 |
23 | values = {}
24 | values[directions.NORTH] = Vector2(0, -1)
25 | values[directions.SOUTH] = Vector2(0, 1)
26 | values[directions.WEST] = Vector2(-1, 0)
27 | values[directions.EAST] = Vector2(1, 0)
28 |
29 | @staticmethod
30 | def choice():
31 | return choice(Cardinal.values.values())
32 |
33 | @staticmethod
34 | def toward(pos, tar):
35 | diff = Vector2(tar)
36 | diff -= pos
37 | if pos == tar:
38 | return None
39 |
40 | if abs(diff[0]) > abs(diff[1]):
41 | if diff[0] < 0:
42 | return Cardinal.directions.WEST
43 | else:
44 | return Cardinal.directions.EAST
45 | else:
46 | if diff[1] < 0:
47 | return Cardinal.directions.NORTH
48 | else:
49 | return Cardinal.directions.SOUTH
50 |
51 | if __name__ == '__main__' :
52 | print K_UP in Cardinal.key_map.keys()
53 |
--------------------------------------------------------------------------------
/src/ai/task/follow.py:
--------------------------------------------------------------------------------
1 | '''
2 | Created on 2013-05-25
3 |
4 | @author: brian
5 | '''
6 |
7 | from src.util import Vector2
8 |
9 | class Follow(object):
10 |
11 | def __init__(self, actor):
12 | self.actor = actor
13 | self.target = None
14 |
15 | def condition(self):
16 |
17 | self.target = None
18 |
19 | leader = self.actor.getLeader()
20 | if leader is not None:
21 | if self.actor.distance(leader.position) > 3:
22 | self.target = leader
23 | else:
24 | pass #TODO: Follow leader mob type
25 |
26 | return self.target is not None
27 |
28 | def do(self):
29 |
30 | x = self.target.position[0] - self.actor.position[0]
31 | y = self.target.position[1] - self.actor.position[1]
32 |
33 | direction = (self.sign(x), 0) if abs(x) > abs(y) else (0, self.sign(y))
34 | alternative = (self.sign(x), 0) if abs(x) <= abs(y) else (0, self.sign(y))
35 |
36 | pos = self.actor.position
37 |
38 | if self.actor.world.isLocationPassable(Vector2(int(pos[0] + direction[0]), int(pos[1] + direction[1]))):
39 | self.actor.move(direction)
40 | else:
41 | self.actor.move(alternative)
42 |
43 |
44 | @staticmethod
45 | def sign(n):
46 | return -1 if n < 0 else 1
47 |
48 |
--------------------------------------------------------------------------------
/src/inventory/inventory.py:
--------------------------------------------------------------------------------
1 | '''
2 | Created on 2013-06-01
3 |
4 | @author: brian
5 | '''
6 |
7 | class Inventory(object):
8 |
9 | def __init__(self, owner, itemFactory):
10 | self.bag = [None] * 8
11 | self.bar = [None] * 8
12 | self.owner = owner
13 | self.itemFactory = itemFactory
14 |
15 |
16 |
17 |
18 | def load(self, data):
19 |
20 | if 'bag' in data.keys():
21 | bag = data['bag']
22 | for i in range(len(bag)):
23 | if bag[i] is None:
24 | self.bag[i] = None
25 | else:
26 | self.bag[i] = self.itemFactory.load(bag[i])
27 |
28 | if 'bar' in data.keys():
29 | bar = data['bar']
30 | for i in range(len(bar)):
31 | if bar[i] is None:
32 | self.bar[i] = None
33 | else:
34 | self.bar[i] = self.itemFactory.load(bar[i])
35 |
36 |
37 | def pocket(self, id):
38 |
39 | for i in range(len(self.bag)):
40 | if self.bag[i] is None:
41 | self.bag[i] = self.itemFactory.getStack(id)
42 | return
43 | if self.bag[i].id is id:
44 | self.bag[i].size += 1
45 | return
46 |
47 |
48 | def save(self):
49 | data = {}
50 |
51 | bag = []
52 | for i in self.bag:
53 | if i is None:
54 | bag.append(None)
55 | else:
56 | bag.append(i.save())
57 | data['bag'] = bag
58 |
59 | bar = []
60 | for i in self.bar:
61 | if i is None:
62 | bar.append(None)
63 | else:
64 | bar.append(i.save())
65 | data['bar'] = bar
66 |
67 | return data
68 |
--------------------------------------------------------------------------------
/src/ai/task/flee.py:
--------------------------------------------------------------------------------
1 | '''
2 | Created on 2013-05-25
3 |
4 | @author: brian
5 | '''
6 |
7 | '''
8 | Created on 2013-05-25
9 |
10 | @author: brian
11 | '''
12 |
13 | class Flee(object):
14 |
15 | def __init__(self, actor):
16 | self.actor = actor
17 | self.target = None
18 |
19 | def condition(self):
20 |
21 | if float(self.actor.health) / self.actor.maxHealth > 0.2:
22 | return False
23 |
24 | self.target = None
25 |
26 | for entity in self.actor.getEnemies():
27 | if self.actor.canSee(entity.position):
28 | self.target = entity
29 |
30 | return self.target is not None
31 |
32 | def do(self):
33 |
34 | x = self.actor.position[0] - self.target.position[0]
35 | y = self.actor.position[1] - self.target.position[1]
36 |
37 | direction = (self.sign(x), 0) if abs(x) > abs(y) else (0, self.sign(y))
38 | alternative = (self.sign(x), 0) if abs(x) <= abs(y) else (0, self.sign(y))
39 |
40 | pos = self.actor.position
41 |
42 | if self.actor.world.isLocationPassable((pos[0] + direction[0], pos[1] + direction[1])):
43 | self.actor.move(direction)
44 | else:
45 | self.actor.move(alternative)
46 |
47 |
48 | @staticmethod
49 | def sign(n):
50 | return -1 if n < 0 else 1
51 |
52 |
--------------------------------------------------------------------------------
/src/entity/mobspawner.py:
--------------------------------------------------------------------------------
1 | '''
2 | Created on 2013-05-16
3 |
4 | @author: brian
5 | '''
6 |
7 | from random import choice
8 | from random import randint
9 | from mobs import hostiles
10 |
11 | class MobSpawner(object):
12 |
13 | maxMobs = 40
14 | spawnFrequency = 10
15 |
16 | def __init__(self, world):
17 | self.world = world
18 |
19 | def spawn(self):
20 |
21 | if len(self.world.mobManager.mobs) >= self.maxMobs:
22 | return
23 |
24 | if not (randint(0, self.spawnFrequency) == 0):
25 | return
26 |
27 | mob = self.getRandomMob()
28 |
29 | found = self.getSpawnLocation(mob)
30 |
31 | if not found:
32 | return
33 |
34 | self.world.mobManager.addMob(mob)
35 |
36 | def getRandomMob(self):
37 |
38 | mobtype = choice(hostiles)
39 | mob = mobtype(self.world)
40 | mob.equip()
41 | return mob
42 |
43 | def getSpawnLocation(self, mob):
44 |
45 | found = False
46 | attempts = 0
47 |
48 | while not found and attempts < 20:
49 | found = True
50 | chunk = self.world.chunkManager.getRandomChunk()
51 | chunkX, chunkY = chunk.getPos()
52 | posX = chunkX << 4
53 | posY = chunkY << 4
54 | mob.position = (posX + randint(0,15), posY + randint(0,15))
55 | if not mob.canSpawn(mob.position):
56 | found = False
57 | for e in self.world.friendly:
58 | if e.canSee(mob.position):
59 | found = False
60 | attempts += 1
61 |
62 | return found
63 |
64 |
65 |
66 |
67 |
68 |
--------------------------------------------------------------------------------
/src/player/targetcontrol.py:
--------------------------------------------------------------------------------
1 | import pygame
2 | from src.util import Vector2
3 | from src.util import Cardinal
4 | from src.actions import Cast
5 |
6 | # observer of mouse events on viewport spaces
7 | class TargetControl(object):
8 |
9 | def __init__(self, player):
10 | self.player = player
11 |
12 | def notify(self, cell, event):
13 |
14 | if event.type == pygame.MOUSEBUTTONUP and event.button == 3:
15 | spell = self.player.avatar.quickcast
16 | caster = self.player.avatar
17 | if self.player.action is None and spell is not None:
18 | tar = Vector2(cell.rel)
19 | tar += caster.position
20 | if(spell.inRange(tar)):
21 | self.player.action = Cast(self.player, spell)
22 | self.player.target = tar
23 | return
24 |
25 | if event.type == pygame.MOUSEBUTTONUP and event.button == 1:
26 |
27 | tar = Vector2(cell.rel)
28 | tar += self.player.avatar.position
29 |
30 | if self.player.action is not None:
31 | self.player.target = tar
32 | return
33 |
34 | if tar == self.player.avatar.position:
35 | return
36 |
37 | dir = Cardinal.toward(self.player.avatar.position, tar)
38 | self.player.move(dir)
39 |
40 |
41 | if event.type == pygame.MOUSEMOTION:
42 | if self.player.action is None:
43 | return
44 |
45 | tar = Vector2(cell.rel)
46 | tar += self.player.avatar.position
47 |
48 | if self.player.action.inRange(tar):
49 | self.player.reticle = Vector2(cell.rel)
50 |
--------------------------------------------------------------------------------
/src/game.py:
--------------------------------------------------------------------------------
1 | import time
2 | import pygame
3 |
4 | from world import World
5 | from gui import Gameview
6 | from player import Player
7 | from images import ImageCache
8 | from sounds import SoundCache
9 |
10 | class Game(object):
11 |
12 | FPS = 30
13 |
14 | def __init__(self):
15 |
16 | pygame.init()
17 | pygame.display.set_caption("RPG Game")
18 | self.seed = 26281376
19 | self.world = World(self.seed, SoundCache())
20 | self.user = Player(self)
21 | self.view = Gameview(self.world, self.user, ImageCache(), self.debug)
22 |
23 | self.clock = pygame.time.Clock()
24 | self.renderTime = 0
25 | self.processTime = 0
26 |
27 | def run(self):
28 |
29 | while(True):
30 |
31 | self.clock.tick(Game.FPS)
32 | t = time.time()
33 | self.view.draw()
34 | self.renderTime = time.time() - t
35 |
36 | t = time.time()
37 | if not self.world.update():
38 | continue
39 |
40 | if self.user.turn():
41 | continue
42 |
43 | self.world.turn()
44 | self.user.save()
45 | self.processTime = time.time() - t
46 |
47 | def debug(self):
48 | msg = []
49 | msg.append(['FPS: ' + str(int(self.clock.get_fps()))])
50 | msg.append(['Mobs: ' + str(len(self.world.mobManager.mobs))])
51 | msg.append(['Chunks: ' + str(len(self.world.chunkManager.chunkCache))])
52 | msg.append(['Render: ' + str(int(self.renderTime * 1000)) + 'ms'])
53 | msg.append(['World: ' + str(int(self.processTime * 1000)) + 'ms'])
54 | msg.append(['Time: ' + str(self.world.time)])
55 | return msg
56 |
--------------------------------------------------------------------------------
/src/util/vector.py:
--------------------------------------------------------------------------------
1 | import math
2 |
3 |
4 | class Vector2(object):
5 |
6 | def __init__(self, x, y=None):
7 | if(y is None):
8 | self.x = float(x[0])
9 | self.y = float(x[1])
10 | else:
11 | self.x = float(x)
12 | self.y = float(y)
13 |
14 | def dist(self, other):
15 | relx = abs(self.x - other[0])
16 | rely = abs(self.y - other[1])
17 | return math.sqrt(relx**2 + rely**2)
18 |
19 | def inRange(self, range):
20 | return self.x * self.x + self.y * self.y <= range * range + 1
21 |
22 | def __add__(self, other):
23 | self.x += other[0]
24 | self.y += other[1]
25 | return self
26 |
27 | def __sub__(self, other):
28 | self.x -= other[0]
29 | self.y -= other[1]
30 | return self
31 |
32 | def __mul__(self, other):
33 | self.x *= other[0]
34 | self.y *= other[1]
35 | return self
36 |
37 | def __eq__(self, other):
38 | if self.x != other[0]:
39 | return False
40 |
41 | if self.y != other[1]:
42 | return False
43 |
44 | return True
45 |
46 | def center(self):
47 | self.x = math.floor(self.x) + 0.5
48 | self.y = math.floor(self.y) + 0.5
49 |
50 | def save(self):
51 | return Vector2.save(self)
52 |
53 | def __getitem__(self, key):
54 | return (self.x, self.y)[key]
55 |
56 | def __str__(self):
57 | return str(self.x) + ' ' + str(self.y)
58 |
59 | @staticmethod
60 | def save(vec):
61 | data = {}
62 | data["x"] = vec[0]
63 | data["y"] = vec[1]
64 | return data
65 |
66 | @staticmethod
67 | def load(data):
68 | return Vector2(data["x"], data["y"])
69 |
--------------------------------------------------------------------------------
/src/gui/displaypanel.py:
--------------------------------------------------------------------------------
1 | import pygame
2 |
3 | from src.util import Vector2
4 | from cell import Cell
5 | from status import Status
6 | from options import Options
7 | from output import Output
8 |
9 | class DisplayPanel(object):
10 |
11 | def __init__(self, surface, pos, player, images):
12 | self.surface = surface
13 | self.pos = pos
14 | self.player = player
15 | self.images = images
16 |
17 | self.optionsPos = (0,0)
18 | self.optionsRect = pygame.Rect(self.optionsPos, (Cell.size * 12, Cell.size * 6))
19 | self.options = Options(self.surface.subsurface(self.optionsRect), self.optionsPos, self.player, images)
20 |
21 | self.statusPos = (0, Cell.size * 6 + Cell.size)
22 | self.statusRect = pygame.Rect(self.statusPos, (Cell.size * 12, Cell.size))
23 | self.status = Status(self.surface.subsurface(self.statusRect), self.statusPos, self.player, images)
24 |
25 | self.logPos = (0, Cell.size * 7 + (Cell.size * 2))
26 | self.logRect = pygame.Rect(self.logPos, (Cell.size * 12, Cell.size * 8))
27 | self.log = Output(self.surface.subsurface(self.logRect), self.logPos, self.player.log)
28 |
29 |
30 | def draw(self):
31 | self.options.draw()
32 | self.status.draw()
33 | self.log.draw()
34 |
35 | def notify(self, pos, event):
36 | vec = Vector2(pos)
37 | vec -= self.pos
38 |
39 | point = (int(vec[0]), int(vec[1]))
40 | if self.optionsRect.collidepoint(point):
41 | self.options.notify(point, event)
42 |
43 | if self.statusRect.collidepoint(point):
44 | self.status.notify(point, event)
45 |
46 | if self.logRect.collidepoint(point):
47 | self.log.notify(point, event)
48 |
49 |
50 |
--------------------------------------------------------------------------------
/src/world/terrain/chunkmanager.py:
--------------------------------------------------------------------------------
1 | from random import choice
2 | from mapcache import MapCache
3 |
4 | from chunk import Chunk
5 |
6 | class ChunkManager:
7 |
8 | def __init__(self, world):
9 | self.world = world
10 | self.chunkCache = []
11 | self.mapCache = MapCache(self, self.world.seed)
12 | self.maxCacheSize = 64
13 |
14 | def getChunk(self, x, y):
15 | chunkX = int(x) >> 4
16 | chunkY = int(y) >> 4
17 |
18 | for c in self.chunkCache:
19 | if c.getPos() == (chunkX, chunkY):
20 | return c
21 |
22 | toLoad = Chunk((chunkX, chunkY), self.world.getSeed(), self.world.mobManager, self.mapCache)
23 | self.chunkCache.append(toLoad)
24 |
25 | if len(self.chunkCache) > self.maxCacheSize:
26 | toUnload = self.chunkCache.popleft()
27 | toUnload.unload()
28 |
29 | return toLoad
30 |
31 | def getMap(self, x, y):
32 | return self.mapCache.get(x, y)
33 |
34 | def getTile(self, pos):
35 | x = int(pos[0])
36 | y = int(pos[1])
37 | c = self.getChunk(x, y)
38 | return c.getTile(x % Chunk.size, y % Chunk.size)
39 |
40 | def isLoaded(self, x, y):
41 |
42 | for c in self.chunkCache:
43 | if c.pos is (x, y):
44 | return True
45 |
46 | return False
47 |
48 | def setTile(self, (x, y), id):
49 | c = self.getChunk(x, y)
50 | c.setTile((x, y), id)
51 |
52 |
53 | def saveChunks(self):
54 | for c in self.chunkCache:
55 | c.unload()
56 |
57 | def getRandomChunk(self):
58 | return choice(self.chunkCache)
59 |
60 | def cull(self, center, dist):
61 |
62 | for c in self.chunkCache:
63 | if c.getDistToChunk(center) > dist:
64 | c.unload()
65 | self.chunkCache.remove(c)
66 |
--------------------------------------------------------------------------------
/src/ai/task/pursue.py:
--------------------------------------------------------------------------------
1 | '''
2 | Created on 2013-05-25
3 |
4 | @author: brian
5 | '''
6 |
7 | from src.util import Vector2
8 |
9 | class Pursue(object):
10 |
11 | def __init__(self, actor):
12 | self.actor = actor
13 | self.target = None
14 |
15 | def condition(self):
16 |
17 | if not self.actor.hostile:
18 | return False
19 |
20 | self.target = None
21 |
22 | for entity in self.actor.getEnemies():
23 | if self.actor.group is None:
24 | canSee = self.actor.canSee
25 | else:
26 | canSee = self.actor.group.canSee
27 |
28 | if canSee(entity.position) and entity.isAlive():
29 |
30 | for ability in self.actor.abilities:
31 | if ability.inRange(entity.position):
32 | if not ability.valid(entity):
33 | continue
34 |
35 | if self.target is None:
36 | self.target = entity
37 | continue
38 |
39 | if self.actor.distance(entity.position) < self.actor.distance(self.target.position):
40 | self.target = entity
41 |
42 |
43 | return self.target is not None
44 |
45 | def do(self):
46 |
47 | x = self.target.position[0] - self.actor.position[0]
48 | y = self.target.position[1] - self.actor.position[1]
49 |
50 | direction = (self.sign(x), 0) if abs(x) > abs(y) else (0, self.sign(y))
51 | alternative = (self.sign(x), 0) if abs(x) <= abs(y) else (0, self.sign(y))
52 |
53 | pos = Vector2(self.actor.position)
54 | pos += Vector2(direction)
55 |
56 |
57 | if self.actor.world.isLocationPassable(pos):
58 | self.actor.move(Vector2(direction))
59 | else:
60 | self.actor.move(Vector2(alternative))
61 |
62 |
63 | @staticmethod
64 | def sign(n):
65 | return -1 if n < 0 else 1
66 |
67 |
--------------------------------------------------------------------------------
/src/entity/mobmanager.py:
--------------------------------------------------------------------------------
1 | '''
2 | Created on 2013-05-15
3 |
4 | @author: brian
5 | '''
6 |
7 | from mobspawner import MobSpawner
8 | from mobs import lookup
9 |
10 | class MobManager(object):
11 |
12 | def __init__(self, world):
13 | self.world = world
14 | self.mobs = []
15 | self.mobSpawner = MobSpawner(self.world)
16 |
17 | def addMob(self, mob):
18 | self.mobs.append(mob)
19 |
20 | def update(self):
21 | for mob in self.mobs:
22 | mob.update()
23 |
24 | for mob in self.mobs:
25 | if mob.deathTimer <= 0:
26 | self.mobs.remove(mob)
27 |
28 | def turn(self):
29 |
30 | self.mobSpawner.spawn()
31 |
32 | for mob in self.mobs:
33 | if self.isPlayerInRange(mob):
34 | mob.turn()
35 |
36 | for mob in self.mobs:
37 | if mob.deathTimer <= 0:
38 | self.mobs.remove(mob)
39 |
40 | def save(self, chunkPos):
41 |
42 | mobData = []
43 | for mob in self.mobs:
44 | if mob.inChunk(chunkPos):
45 | mobData.append(mob.save())
46 |
47 | return mobData
48 |
49 | def isPlayerInRange(self, entity):
50 |
51 | for e in self.world.friendly:
52 | if e.distance(entity.position) < 40:
53 | return True
54 |
55 | return False
56 |
57 | def unload(self, chunkPos):
58 |
59 | toRemove = []
60 |
61 | for i in range(len(self.mobs)):
62 | if self.mobs[i].inChunk(chunkPos):
63 | toRemove.append(i)
64 |
65 | toRemove.reverse()
66 | for i in toRemove:
67 | del self.mobs[i]
68 |
69 | def loadEntity(self, data):
70 | mobType = data['type']
71 | e = lookup[mobType](self.world)
72 | e.load(data)
73 | return e
74 |
75 | def load(self, mobData):
76 | for data in mobData:
77 | mob = self.loadEntity(data)
78 | self.mobs.append(mob)
79 |
80 |
--------------------------------------------------------------------------------
/src/abilities/resurrection.py:
--------------------------------------------------------------------------------
1 | '''
2 | Created on 2013-06-04
3 |
4 | @author: brian
5 | '''
6 |
7 | from pygame.color import THECOLORS
8 | from projectiles import Star
9 | from random import randint
10 |
11 | class Resurrection(object):
12 |
13 | range = 5
14 | color = THECOLORS['palegoldenrod']
15 | heal = True
16 | name = "Ressurection"
17 | icon = "ankh"
18 | cooldown = 0
19 |
20 | def __init__(self, caster, location, item):
21 | self.range = self.__class__.range
22 | self.caster = caster
23 | self.target = location
24 | self.done = False
25 | self.entityHit = self.caster.world.getEntityFromLocation(self.target, False)
26 | self.projectile = Star(caster.position, location, self.__class__.color, self.fire)
27 | casterName = self.caster.getName()
28 | if not self.entityHit is None:
29 | targetName = self.entityHit.getName()
30 | self.caster.world.log.append(casterName + ' cast resurrection on ' + targetName)
31 | else:
32 | self.caster.world.log.append(casterName + ' cast resurrection on nothing!')
33 |
34 | def update(self):
35 | self.projectile.update()
36 |
37 | if not self.projectile.done:
38 | return False
39 |
40 | if not self.entityHit is None:
41 | self.entityHit.revive(self.caster)
42 |
43 | self.done = True
44 | return True
45 |
46 | def draw(self, surface, position, visible):
47 | self.projectile.draw(surface, position, visible)
48 |
49 | @classmethod
50 | def validTarget(cls, actor, target):
51 |
52 | if not target in actor.getFriends():
53 | return False
54 |
55 | if target.isAlive():
56 | return False
57 |
58 | if not actor.partyCanSee(target.position):
59 | return False
60 |
61 | return True
62 |
63 | def fire(self):
64 | self.caster.world.sounds.get("resurrection.wav").play()
65 |
--------------------------------------------------------------------------------
/src/entity/mobs/skeleton.py:
--------------------------------------------------------------------------------
1 | '''
2 | Created on 2013-05-28
3 |
4 | @author: brian
5 | '''
6 |
7 | import pygame
8 | from random import randint
9 | from random import choice
10 |
11 | from entity import Entity
12 | from src.ai import task
13 | from src.abilities import Ability
14 |
15 | class Skeleton(Entity):
16 |
17 | living = "skeleton"
18 | dead = "gore"
19 |
20 |
21 | def __init__(self, world):
22 | Entity.__init__(self, world)
23 | self.world = world
24 | self.health = self.maxHealth = 20
25 | self.hostile = True
26 | self.singular = 'a skeleton'
27 |
28 |
29 | class SkeletalWarrior(Skeleton):
30 |
31 | def __init__(self, world):
32 | Skeleton.__init__(self, world)
33 | self.ai.addAI(task.Cast(self))
34 | self.ai.addAI(task.Pursue(self))
35 | self.ai.addAI(task.Wander(self))
36 |
37 | def equip(self):
38 | pass
39 |
40 | class SkeletalArcher(Skeleton):
41 |
42 | def __init__(self, world):
43 | Skeleton.__init__(self, world)
44 | self.ai.addAI(task.Fallback(self))
45 | self.ai.addAI(task.Cast(self))
46 | self.ai.addAI(task.Pursue(self))
47 | self.ai.addAI(task.Wander(self))
48 |
49 | def equip(self):
50 | self.abilities = [Ability(self, Ability.lookup["BowShot"])]
51 |
52 |
53 | class SkeletalMage(Skeleton):
54 |
55 | def __init__(self, world):
56 | Skeleton.__init__(self, world)
57 | self.ai.addAI(task.Fallback(self))
58 | self.ai.addAI(task.Cast(self))
59 | self.ai.addAI(task.Pursue(self))
60 | self.ai.addAI(task.Wander(self))
61 |
62 | def equip(self):
63 | if randint(0, 5) == 0:
64 | self.abilities = [Ability(self, Ability.lookup["Explosion"]), Ability(self, Ability.lookup["FireBall"])]
65 | return
66 | self.abilities = [Ability(self, Ability.lookup["ChainBolt"]), Ability(self, Ability.lookup["MagicMissile"])]
67 |
--------------------------------------------------------------------------------
/src/gui/options.py:
--------------------------------------------------------------------------------
1 | '''
2 | Created on 2013-05-03
3 |
4 | @author: brian
5 | '''
6 |
7 |
8 | import pygame
9 | from pygame.locals import *
10 | from pygame.color import THECOLORS
11 | from src.util import Vector2
12 |
13 | from cell import Cell
14 | from card import Card
15 |
16 | class Options(object):
17 |
18 | def __init__(self, surface, pos, player, images):
19 |
20 | self.surface = surface
21 | self.pos = pos
22 | self.player = player
23 | self.tileSize = 32
24 |
25 | self.party = []
26 | for i in range(6):
27 | p = (0, self.tileSize * i)
28 | rect = pygame.Rect(p, (Cell.size * 12, self.tileSize))
29 | self.party.append(Card(self.surface.subsurface(rect), p, i, player, images))
30 | self.selected = 0
31 | self.player = player
32 |
33 | self.fontobject = pygame.font.Font(None,24)
34 |
35 | def draw(self):
36 | pygame.event.pump()
37 | pressed = pygame.key.get_pressed()
38 |
39 | self.surface.fill(THECOLORS["black"])
40 | if not hasattr(self.player.action, 'options'):
41 | self.drawParty()
42 | return
43 |
44 | options = self.player.action.options
45 | if options is None:
46 | self.drawParty()
47 | return
48 |
49 | count = 0
50 | for line in options:
51 | self.surface.blit(self.fontobject.render('>> ' + str(line) + ' - ' + str(options[line]), 1, (255,255,255)), (0, 16*count))
52 | count += 1
53 |
54 | def drawParty(self):
55 | count = 0
56 | for i, e in enumerate(self.player.world.friendly.members):
57 | self.party[i].draw()
58 |
59 |
60 | def getMaxLines(self):
61 | return self.surface.get_height()/16
62 |
63 | def notify(self, pos, event):
64 | v = Vector2(pos)
65 | v -= self.pos
66 | y = int(v[1] / 32)
67 | if y in range(len(self.party)):
68 | card = self.party[y]
69 | card.notify(v, event)
70 |
71 |
72 |
--------------------------------------------------------------------------------
/src/abilities/healbolt.py:
--------------------------------------------------------------------------------
1 | '''
2 | Created on 2013-05-27
3 |
4 | @author: brian
5 | '''
6 | from pygame.color import THECOLORS
7 |
8 | from random import randint
9 | from projectiles import Star
10 |
11 | class HealBolt(object):
12 |
13 | range = 5
14 | damage = 2, 5
15 | heal = True
16 | name = "Heal Bolt"
17 | icon = "healbolt"
18 | cooldown = 0
19 |
20 | def __init__(self, caster, location, item):
21 | self.item = item
22 | self.caster = caster
23 | self.target = location
24 | self.done = False
25 | self.color = THECOLORS['yellow']
26 | self.entityHit = self.caster.world.getEntityFromLocation(self.target)
27 | self.projectile = Star(caster.position, self.target, self.color, self.fire)
28 | casterName = self.caster.getName()
29 | if not self.entityHit is None:
30 | targetName = self.entityHit.getName()
31 | self.caster.world.log.append(casterName + ' cast Heal at ' + targetName)
32 | else:
33 | self.caster.world.log.append(casterName + ' cast Heal at nothing!')
34 |
35 | def update(self):
36 | pos = None
37 | if self.entityHit is not None:
38 | pos = self.entityHit.position
39 | self.projectile.update(pos)
40 | if self.projectile.done:
41 | if not self.entityHit is None:
42 | self.entityHit.heal(self.caster, randint(self.damage[0], self.damage[1]))
43 | self.done = True
44 | return True
45 | return False
46 |
47 | def draw(self, surface, position, visible):
48 | if not self.done:
49 | self.projectile.draw(surface, position, visible)
50 |
51 | @classmethod
52 | def validTarget(cls, actor, target):
53 |
54 | if not target in actor.getFriends():
55 | return False
56 |
57 | if not target.isAlive():
58 | return False
59 |
60 | if target.health / target.maxHealth > 0.8:
61 | return False
62 |
63 | if not actor.partyCanSee(target.position):
64 | return False
65 |
66 | return True
67 |
68 |
69 | def fire(self):
70 | self.caster.world.sounds.get("spell-healing.wav").play()
71 |
72 |
73 |
--------------------------------------------------------------------------------
/src/abilities/rainbowbolt.py:
--------------------------------------------------------------------------------
1 | '''
2 | Created on 2013-06-04
3 |
4 | @author: brian
5 | '''
6 |
7 | from random import randint
8 | from projectiles import Rainbolt
9 | from pygame.color import THECOLORS
10 |
11 | class RainbowBolt(object):
12 |
13 | range = 7
14 | damage = 2, 5
15 | heal = False
16 | name = "Rainbow Bolt"
17 | icon = "rainbolt"
18 | cooldown = 0
19 |
20 | def __init__(self, caster, location, item):
21 | self.item = item
22 | self.caster = caster
23 | self.target = location
24 | self.range = self.__class__.range
25 | self.damage = self.__class__.damage
26 | casterName = self.caster.getName()
27 | self.entityHit = self.caster.world.getEntityFromLocation(self.target)
28 | if not self.entityHit is None:
29 | targetName = self.entityHit.getName()
30 | self.caster.world.log.append(casterName + ' cast ' + self.__class__.__name__ + ' at ' + targetName)
31 | else:
32 | self.caster.world.log.append(casterName + ' cast ' + self.__class__.__name__ + ' at nothing!')
33 |
34 | self.projectile = Rainbolt(caster.position, location, self.fire, self.impact)
35 | self.done = False
36 |
37 | def update(self):
38 | self.projectile.update()
39 | if self.projectile.done:
40 | if not self.entityHit is None:
41 | self.entityHit.inflict(self.caster, randint(self.damage[0], self.damage[1]))
42 | self.done = True
43 | return True
44 |
45 | return False
46 |
47 | def draw(self, surface, position, visible):
48 | if not self.done:
49 | self.projectile.draw(surface, position, visible)
50 |
51 | @classmethod
52 | def validTarget(cls, actor, target):
53 |
54 | if not target in actor.getEnemies():
55 | return False
56 |
57 | if not target.isAlive():
58 | return False
59 |
60 | if not actor.partyCanSee(target.position):
61 | return False
62 |
63 | return True
64 |
65 | def fire(self):
66 | self.caster.world.sounds.get("magic-missile.wav").play()
67 |
68 | def impact(self):
69 | self.caster.world.sounds.get("fireball-impact.wav").play()
70 |
--------------------------------------------------------------------------------
/src/gui/gameview.py:
--------------------------------------------------------------------------------
1 | import pygame
2 | from pygame.color import THECOLORS
3 | import time
4 |
5 |
6 | from cell import Cell
7 | from viewport import Viewport
8 | from displaypanel import DisplayPanel
9 |
10 |
11 | class Gameview(object):
12 |
13 | def __init__(self, world, player, images, debug=None):
14 | self.world = world
15 | self.player = player
16 | self.screenSize = (1024, 600)
17 | self.surface = pygame.display.set_mode(self.screenSize)
18 | self.images = images
19 | self.debug = debug
20 | self.renderTime = 0
21 | self.boxes = []
22 |
23 | self.viewportPos = (28, 28)
24 | viewportLength = Viewport.size * Cell.size
25 | self.viewportRect = pygame.Rect(self.viewportPos, (viewportLength, viewportLength))
26 | self.boxes.append(self.viewportRect)
27 | self.viewport = Viewport(self.surface.subsurface(self.viewportRect), self.viewportPos, world, player, images)
28 |
29 | self.displayPos = (viewportLength + 56, 28)
30 | self.displayRect = pygame.Rect(self.displayPos, (Cell.size * 12, viewportLength))
31 | self.display = DisplayPanel(self.surface.subsurface(self.displayRect), self.displayPos, self.player, images)
32 |
33 | player.screenshot = self.printscreen
34 |
35 | def draw(self):
36 |
37 | self.surface.fill(THECOLORS["royalblue4"])
38 | self.viewport.draw()
39 | self.display.draw()
40 | if self.debug is not None and self.player.debug:
41 | self.viewport.display(self.debug())
42 |
43 | pygame.display.flip()
44 |
45 | def notify(self, pos, event):
46 | if self.viewportRect.collidepoint(pos):
47 | self.viewport.notify(pos, event)
48 | if self.displayRect.collidepoint(pos):
49 | self.display.notify(pos, event)
50 |
51 | def printscreen(self):
52 | date = time.gmtime()
53 | fileName = "screenshot_" + \
54 | str(date[0]) + '-' + \
55 | str(date[1]) + '-' + \
56 | str(date[2]) + '-' + \
57 | str(date[3]-8) + '-' + \
58 | str(date[4]) + '-' + \
59 | str(date[5]) + \
60 | '.jpg'
61 |
62 | pygame.image.save(self.surface, fileName)
63 |
--------------------------------------------------------------------------------
/src/abilities/poisonbolt.py:
--------------------------------------------------------------------------------
1 | '''
2 | Created on 2013-06-04
3 |
4 | @author: brian
5 | '''
6 |
7 | from random import randint
8 | from projectiles import Star
9 | from pygame.color import THECOLORS
10 |
11 | class PoisonBolt(object):
12 |
13 | range = 4
14 | damage = 1, 4
15 | heal = False
16 | name = "Poison bolt"
17 | color = THECOLORS['green']
18 | icon = "magicbolt"
19 | cooldown = 0
20 |
21 | def __init__(self, caster, location, item):
22 | self.item = item
23 | self.caster = caster
24 | self.target = location
25 | self.range = self.__class__.range
26 | self.damage = self.__class__.damage
27 | self.color = self.__class__.color
28 | casterName = self.caster.getName()
29 | self.entityHit = self.caster.world.getEntityFromLocation(self.target)
30 | if not self.entityHit is None:
31 | targetName = self.entityHit.getName()
32 | self.caster.world.log.append(casterName + ' cast ' + self.__class__.__name__ + ' at ' + targetName)
33 | else:
34 | self.caster.world.log.append(casterName + ' cast ' + self.__class__.__name__ + ' at nothing!')
35 |
36 | self.projectile = Star(caster.position, location, self.color, self.fire, self.impact)
37 | self.done = False
38 |
39 | def update(self):
40 | self.projectile.update()
41 | if self.projectile.done:
42 | if not self.entityHit is None:
43 | self.entityHit.inflict(self.caster, randint(self.damage[0], self.damage[1]))
44 | self.done = True
45 | return True
46 |
47 | return False
48 |
49 | def draw(self, surface, position, visible):
50 | if not self.done:
51 | self.projectile.draw(surface, position, visible)
52 |
53 | @classmethod
54 | def validTarget(cls, actor, target):
55 |
56 | if not target in actor.getEnemies():
57 | return False
58 |
59 | if not target.isAlive():
60 | return False
61 |
62 | if not actor.partyCanSee(target.position):
63 | return False
64 |
65 | return True
66 |
67 | def fire(self):
68 | self.caster.world.sounds.get("magic-missile.wav").play()
69 |
70 | def impact(self):
71 | self.caster.world.sounds.get("fireball-impact.wav").play()
72 |
--------------------------------------------------------------------------------
/src/abilities/bowshot.py:
--------------------------------------------------------------------------------
1 | '''
2 | Created on 2013-05-28
3 |
4 | @author: brian
5 | '''
6 |
7 | from pygame.color import THECOLORS
8 | from projectiles import Arrow
9 | from random import randint
10 | from src.util import Vector2
11 |
12 | class BowShot(object):
13 |
14 | range = 7
15 | damage = 2, 5
16 | heal = False
17 | name = "Shoot"
18 | icon = "arrow"
19 | cooldown = 0
20 |
21 | def __init__(self, caster, location, item):
22 | self.caster = caster
23 | self.target = location
24 | self.item = item
25 | self.color = THECOLORS['chocolate']
26 | self.range = self.__class__.range
27 | self.damage = self.__class__.damage
28 |
29 | casterName = self.caster.getName()
30 |
31 | self.entityHit = self.caster.world.getEntityFromLocation(self.target)
32 | if not self.entityHit is None:
33 | targetName = self.entityHit.getName()
34 | self.caster.world.log.append(casterName + ' shot ' + targetName + ' with a ' + self.__class__.__name__)
35 | else:
36 | self.caster.world.log.append(casterName + ' shot nothing!')
37 |
38 | self.projectile = Arrow(caster.position, location, self.color, self.fire, self.impact)
39 | self.done = False
40 |
41 | def update(self):
42 | pos = None
43 | if self.entityHit is not None:
44 | pos = Vector2(self.entityHit.position)
45 |
46 | self.projectile.update(pos)
47 | if self.projectile.done:
48 | if not self.entityHit is None:
49 | self.entityHit.inflict(self.caster, randint(self.damage[0], self.damage[1]))
50 | self.done = True
51 | return True
52 |
53 | return False
54 |
55 | def draw(self, surface, position, visible):
56 | if not self.done:
57 | self.projectile.draw(surface, position, visible)
58 |
59 | @classmethod
60 | def validTarget(cls, actor, target):
61 |
62 | if not target.isAlive():
63 | return False
64 |
65 | if target in actor.getFriends():
66 | return False
67 |
68 | return actor.partyCanSee(target.position)
69 |
70 |
71 | def fire(self):
72 | self.caster.world.sounds.get("pew.wav").play()
73 |
74 | def impact(self):
75 | self.caster.world.sounds.get("hit.wav").play()
76 |
--------------------------------------------------------------------------------
/src/world/terrain/chunk.py:
--------------------------------------------------------------------------------
1 | import string
2 | import os
3 | import chunkgen
4 | from tile import Tile
5 | import json
6 | import terrain
7 | import math
8 |
9 |
10 | class Chunk:
11 |
12 | size = 16
13 |
14 | def __init__(self, pos, seed, mobManager, mapCache):
15 | self.seed = seed
16 | self.pos = pos
17 | self.mobs = mobManager
18 | self.tiles = []
19 |
20 | self.saveDir = 'save/world/'
21 | if not os.path.isdir(self.saveDir):
22 | os.mkdir(self.saveDir)
23 |
24 | self.load()
25 | self.mapCache = mapCache
26 | self.mapCache.genMap(self)
27 |
28 |
29 | def getPos(self):
30 | return self.pos
31 |
32 |
33 | def save(self):
34 | chunkData = {}
35 | tileList = []
36 | for tile in self.tiles:
37 | tileList.append(tile.save())
38 | chunkData['tiles'] = tileList
39 | chunkData['mobs'] = self.mobs.save(self.pos)
40 |
41 | x, y = self.pos
42 | fileName = self.saveDir + str(x) + '_' + str(y)
43 | with open(fileName, 'w') as f:
44 | json.dump(chunkData, f, sort_keys=True, indent=4)
45 |
46 |
47 | def unload(self):
48 | self.save()
49 | self.mapCache.genMap(self)
50 | self.mobs.unload(self.pos)
51 |
52 |
53 | def load(self):
54 | x, y = self.pos
55 | fileName = self.saveDir + str(x) + '_' + str(y)
56 | if not os.path.isfile(fileName):
57 | self.generate()
58 | self.save()
59 | return
60 |
61 | with open(fileName, 'r') as f:
62 | chunkData = json.load(f)
63 |
64 | tileList = chunkData['tiles']
65 | for data in tileList:
66 | tileToLoad = Tile()
67 | tileToLoad.load(data)
68 | self.tiles.append(tileToLoad)
69 |
70 | self.mobs.load(chunkData['mobs'])
71 |
72 | def generate(self):
73 | self.tiles = chunkgen.ChunkGen(self.seed, self.pos).generate()
74 |
75 | def getTile(self, x, y):
76 | return self.tiles[y * Chunk.size + x]
77 |
78 | def setTile(self, pos, id):
79 | tile = self.tiles[int(pos[1]) % 16 * Chunk.size + int(pos[0]) % 16]
80 | tile.build(id)
81 | self.mapCache.genMap(self)
82 | self.save()
83 |
84 | def getDistToChunk(self, pos):
85 | x, y = pos
86 | return math.sqrt((self.pos[0] - x)**2 + (self.pos[1] - y)**2)
87 |
--------------------------------------------------------------------------------
/src/world/terrain/mapcache.py:
--------------------------------------------------------------------------------
1 | import os
2 | import pygame
3 |
4 | from simplex import Simplex
5 |
6 | class MapCache(object):
7 |
8 | def __init__(self, chunkManager, seed):
9 |
10 | self.chunkManager = chunkManager
11 | self.mapDir = 'save/map/'
12 | self.noise = Simplex(seed)
13 |
14 | if not os.path.isdir(self.mapDir):
15 | os.mkdir(self.mapDir)
16 |
17 | self.basePath = os.path.dirname(__file__)
18 | self.cache = {}
19 |
20 | def load(self, name):
21 | path = self.mapDir + name + '.png'
22 |
23 | if not os.path.isfile(path):
24 | return
25 |
26 | f = open(path, 'r')
27 | self.cache[name] = pygame.image.load(path).convert_alpha()
28 | return self.cache[name]
29 |
30 | def get(self, x, y):
31 | name = str(x) + '_' + str(y)
32 |
33 | if(name in self.cache):
34 | return self.cache[name]
35 |
36 | if(self.chunkManager.isLoaded(x, y)):
37 | chunk = chunkManager.getChunk(x << 4, y << 4)
38 | self.genMap(chunk)
39 |
40 | return self.load(name)
41 |
42 | def genMap(self, chunk):
43 | img = pygame.Surface((16, 16))
44 |
45 | for y in range(16):
46 | for x in range(16):
47 | t = chunk.getTile(x, y)
48 | groundColor = t.getGround().color()
49 | shadow = self.getShadow(chunk.pos[0] * 16 + x, chunk.pos[1] * 16 + y)
50 | color = self.blend(shadow, groundColor)
51 |
52 | img.set_at((x, y), color)
53 |
54 |
55 | img = pygame.transform.scale2x(img)
56 | x, y = chunk.pos
57 | name = str(x) + '_' + str(y)
58 | path = self.mapDir + name + '.png'
59 | pygame.image.save(img, path)
60 | self.cache[name] = img
61 |
62 | def getShadow(self, x, y):
63 | octaves = 6
64 | persistence = 0.75
65 | scale = 0.004
66 | c1 = self.noise.octave_noise_2d(octaves, persistence, scale, x, y)
67 | if c1 < 0.05:
68 | return 0.5
69 | c1 += 0.4
70 | c2 = self.noise.octave_noise_2d(octaves, persistence, scale, x - 2, y - 2)
71 | c1 = (c1 - c2)
72 | c1 *= 1.2
73 |
74 | if c1 > 1:
75 | c1 = 1
76 | if c1 < 0:
77 | c1 = 0
78 |
79 | return c1
80 |
81 | def blend(self, percent, map):
82 |
83 | r = int(map[0] * percent)
84 | g = int(map[1] * percent)
85 | b = int(map[2] * percent)
86 |
87 | return r, g, b
88 |
--------------------------------------------------------------------------------
/src/actions/build.py:
--------------------------------------------------------------------------------
1 | '''
2 | Created on 2013-05-02
3 |
4 | @author: brian
5 | '''
6 |
7 |
8 | import pygame
9 | from pygame.locals import *
10 | from src.util import Cardinal
11 |
12 | class Build:
13 |
14 | def __init__(self, player):
15 | self.player = player
16 | self.direction = None
17 | self.finished = False
18 | self.player.log.append('Build... (Choose a block)')
19 | self.choices = {}
20 | bag = player.avatar.inventory.bag
21 | for i in range(len(bag)):
22 | if bag[i] is None:
23 | continue
24 |
25 | self.choices[K_0 + i + 1] = bag[i]
26 |
27 | self.choice = None
28 |
29 | self.options = {}
30 | for i in self.choices.iterkeys():
31 | self.options[i - K_0] = str(self.choices[i].size) + " : " + self.player.world.materials[self.choices[i].id].__name__
32 |
33 | def nextStep(self):
34 |
35 | if len(self.choices) is 0:
36 | self.player.log.append('Nothing to build with.')
37 | return True
38 |
39 | if(self.direction is not None and self.choice is not None):
40 | x = self.player.party.getLeader().position[0] + self.direction[0]
41 | y = self.player.party.getLeader().position[1] + self.direction[1]
42 | world = self.player.world
43 | if not world.build((x, y), self.choices[self.choice].id):
44 | self.player.log.append('Cannot build there')
45 | return True
46 | self.choices[self.choice].size -= 1
47 | bag = self.player.avatar.inventory.bag
48 | if bag[self.choice - K_0 - 1].size is 0:
49 | bag[self.choice - K_0 - 1] = None
50 | return True
51 |
52 | e = pygame.event.poll()
53 |
54 | if e is None:
55 | return False
56 |
57 | if e.type != KEYDOWN:
58 | return False
59 |
60 | if(self.choice is None):
61 | if(e.key in self.choices):
62 | self.choice = e.key
63 | self.player.log.append('Build... (Choose a direction)')
64 | elif(e.key == K_ESCAPE):
65 | self.player.log.append('Cancelled')
66 | return True
67 |
68 | return False
69 |
70 | if(self.direction is None):
71 | if(e.key in Cardinal.key_map.keys()):
72 | self.direction = Cardinal.values[Cardinal.key_map[e.key]]
73 | return False
74 | elif(e.key == K_ESCAPE):
75 | self.player.log.append('Cancelled')
76 | return True
77 |
78 | return False
79 |
80 |
--------------------------------------------------------------------------------
/src/abilities/ability.py:
--------------------------------------------------------------------------------
1 | import pygame
2 |
3 | from attack import Attack
4 | from bowshot import *
5 | from magicmissile import *
6 | from chainbolt import ChainBolt
7 | from poisonbolt import PoisonBolt
8 | from healbolt import HealBolt
9 | from resurrection import Resurrection
10 | from explosion import Explosion
11 | from rainbowbolt import RainbowBolt
12 |
13 | class Ability(object):
14 |
15 | lookup = {}
16 | lookup["Attack"] = Attack
17 | lookup["BowShot"] = BowShot
18 | lookup["MagicMissile"] = MagicMissile
19 | lookup["PoisonBolt"] = PoisonBolt
20 | lookup["ChainBolt"] = ChainBolt
21 | lookup["FireBall"] = FireBall
22 | lookup["HealBolt"] = HealBolt
23 | lookup["Resurrection"] = Resurrection
24 | lookup["Explosion"] = Explosion
25 | lookup["RainbowBolt"] = RainbowBolt
26 |
27 | def __init__(self, unit, ability=None):
28 | self.caster = unit
29 | self.ability = ability
30 | self.observers = []
31 | self.cooldown = 0
32 |
33 | def notify(self, event):
34 | for obs in self.observers:
35 | obs.notify(self, event)
36 |
37 | def update(self):
38 | if self.cooldown > 0:
39 | self.cooldown -= 1
40 |
41 | def valid(self, entity):
42 |
43 | if not self.caster.canHit(entity.position, self.ability.range):
44 | return False
45 |
46 | if not self.ability.validTarget(self.caster, entity):
47 | return False
48 |
49 | return True
50 |
51 | def ready(self):
52 |
53 | if self.cooldown > 0:
54 | return False
55 |
56 | return True
57 |
58 | def inRange(self, target):
59 | return self.caster.canHit(target, self.ability.range)
60 |
61 | def cast(self, target):
62 | self.cooldown = self.ability.cooldown
63 | return self.ability(self.caster, target, self.ability)
64 |
65 | def draw(self, images, surface):
66 | image = images.get(self.ability.icon)
67 | surface.blit(image, surface.get_rect())
68 | if not self.ready():
69 | font = pygame.font.Font(None,24)
70 | surface.blit(font.render(str(self.cooldown), 1, (255, 255, 0)), (10, 10))
71 |
72 | def save(self):
73 | data = {}
74 | data['ability'] = self.ability.__name__
75 | data['cooldown'] = self.cooldown
76 | return data
77 |
78 | def load(self, data):
79 | ability = data['ability']
80 | self.ability = Ability.lookup[ability]
81 | self.cooldown = data['cooldown']
82 |
83 |
84 |
--------------------------------------------------------------------------------
/src/gui/card.py:
--------------------------------------------------------------------------------
1 | import pygame
2 | from pygame.color import THECOLORS
3 | from src.util import Vector2
4 | from src.abilities import Ability
5 | from src.actions import Cast
6 |
7 | class Card(object):
8 |
9 | def __init__(self, surface, pos, index, player, images):
10 | self.surface = surface
11 | self.pos = pos
12 | self.player = player
13 | self.index = index
14 | self.images = images
15 | self.font = pygame.font.Font(None,16)
16 | self.size = 32
17 | self.fontobject = pygame.font.Font(None,24)
18 |
19 | def draw(self):
20 | if not self.index in range(len(self.player.party.members)):
21 | return
22 | unit = self.player.party.members[self.index]
23 | self.surface.fill(THECOLORS["black"])
24 | rect = pygame.Rect((0,0), (self.size, self.size))
25 | self.surface.blit(unit.getImage(self.images), rect)
26 |
27 | if unit is self.player.avatar:
28 | color = THECOLORS["gold"]
29 | else:
30 | color = THECOLORS["gray"]
31 |
32 | message = unit.name + ' - ' + str(unit.health) + 'HP'
33 | self.surface.blit(self.fontobject.render(message, 1, color), (42, 0))
34 | message = str(int(unit.position[0])) + ', ' + str(int(unit.position[1]))
35 | self.surface.blit(self.fontobject.render(message, 1, color), (42, 16))
36 |
37 | aOffset = 200
38 | for i, ability in enumerate(unit.abilities):
39 | rect = pygame.Rect((i * self.size + aOffset, 0),(self.size, self.size))
40 | ability.draw(self.images, self.surface.subsurface(rect))
41 | self.surface.blit(self.font.render(str(pygame.key.name(self.player.ABILITY_KEYS[i])).upper(), 1, THECOLORS["gray"]), (i * self.size + aOffset,0))
42 | if unit is self.player.avatar and self.player.action.__class__ is Cast and self.player.action.spell is ability:
43 | pygame.draw.rect(self.surface, THECOLORS["yellow"], rect, 1)
44 | elif unit.quickcast is ability:
45 | pygame.draw.rect(self.surface, THECOLORS["green"], rect, 1)
46 |
47 | def notify(self, pos, event):
48 | v = Vector2(pos)
49 | if not self.index in range(len(self.player.party.members)):
50 | return
51 | unit = self.player.party.members[self.index]
52 | v -= self.pos
53 | if v[0] < 200:
54 | unit.notify(event)
55 |
56 | i = int((v[0] - 200) / 32)
57 | if i in range(len(unit.abilities)):
58 | ability = unit.abilities[i]
59 | ability.notify(event)
60 |
61 |
62 |
--------------------------------------------------------------------------------
/src/actions/cast.py:
--------------------------------------------------------------------------------
1 | '''
2 | Created on 2013-05-26
3 |
4 | @author: brian
5 | '''
6 |
7 | import string
8 |
9 | from src.util import Cardinal
10 | from src.util import Vector2
11 | import pygame
12 |
13 | from pygame.locals import *
14 |
15 | class Cast(object):
16 |
17 | def __init__(self, player, ability):
18 | self.player = player
19 | self.projectile = None
20 | self.spell = ability
21 |
22 | leader = self.player.avatar
23 | last = leader.lastTarget
24 | self.player.reticle = (0,0)
25 |
26 | if last is not None:
27 | if self.spell.valid(last):
28 | self.player.reticle = Vector2(last.position)
29 | self.player.reticle -= leader.position
30 | else:
31 | leader.lastTarget = None
32 |
33 |
34 |
35 | self.player.target = None
36 |
37 | def nextStep(self):
38 |
39 | leader = self.player.avatar
40 |
41 | if self.player.target is not None:
42 | if not self.inRange(self.player.target):
43 | self.player.log.append('Too far away!')
44 | self.player.target = None
45 | return False
46 |
47 | entity = self.player.world.getEntityFromLocation(self.player.target)
48 | if entity is None:
49 | self.player.log.append('Nothing hit!')
50 |
51 | leader.lastTarget = entity
52 | leader.action = self.spell.cast(self.player.target)
53 | return True
54 |
55 |
56 | for e in pygame.event.get():
57 |
58 | if e.type == pygame.MOUSEBUTTONUP or e.type == pygame.MOUSEMOTION:
59 | self.player.mouse_event(e)
60 |
61 | if e.type != KEYDOWN:
62 | continue
63 |
64 | if e.key in [K_ESCAPE]:
65 | self.player.log.append('Cancelled')
66 | return True
67 |
68 | finish = [K_SPACE]
69 | if e.key in finish:
70 | target = Vector2(leader.position)
71 | target += self.player.reticle
72 | self.player.target = target
73 | return False
74 |
75 | if e.key in Cardinal.key_map.keys():
76 | pos = leader.position
77 | direction = Cardinal.values[Cardinal.key_map[e.key]]
78 | newLocation = Vector2(pos)
79 | newLocation += self.player.reticle
80 | newLocation += direction
81 | if not self.spell.inRange(newLocation):
82 | return False
83 | self.player.reticle = Vector2(self.player.reticle)
84 | self.player.reticle += direction
85 | return False
86 |
87 | return False
88 |
89 | def inRange(self, target):
90 | return self.spell.inRange(target)
91 |
92 |
93 |
--------------------------------------------------------------------------------
/src/abilities/magicmissile.py:
--------------------------------------------------------------------------------
1 | '''
2 | Created on 2013-06-04
3 |
4 | @author: brian
5 | '''
6 |
7 | from random import randint
8 | from projectiles import Star
9 | from pygame.color import THECOLORS
10 | from src.util import Vector2
11 |
12 | class MagicMissile(object):
13 |
14 | range = 5
15 | damage = 2, 5
16 | heal = False
17 | name = "Magic Missile"
18 | color = THECOLORS['lightcyan']
19 | icon = "magicbolt"
20 | cooldown = 0
21 |
22 | def __init__(self, caster, location, item):
23 | self.item = item
24 | self.caster = caster
25 | self.target = location
26 | self.range = self.__class__.range
27 | self.damage = self.__class__.damage
28 | self.color = self.__class__.color
29 | casterName = self.caster.getName()
30 | self.entityHit = self.caster.world.getEntityFromLocation(self.target)
31 | if not self.entityHit is None:
32 | targetName = self.entityHit.getName()
33 | self.caster.world.log.append(casterName + ' cast ' + self.__class__.__name__ + ' at ' + targetName)
34 | else:
35 | self.caster.world.log.append(casterName + ' cast ' + self.__class__.__name__ + ' at nothing!')
36 |
37 | self.projectile = Star(caster.position, location, self.color, self.fire, self.impact)
38 | self.done = False
39 |
40 | def update(self):
41 | pos = None
42 | if self.entityHit is not None:
43 | pos = Vector2(self.entityHit.position)
44 | self.projectile.update(pos)
45 | if self.projectile.done:
46 | if not self.entityHit is None:
47 | self.entityHit.inflict(self.caster, randint(self.damage[0], self.damage[1]))
48 | self.done = True
49 | return True
50 |
51 | return False
52 |
53 | def draw(self, surface, position, visible):
54 | if not self.done:
55 | self.projectile.draw(surface, position, visible)
56 |
57 | @classmethod
58 | def validTarget(cls, actor, target):
59 |
60 | if not target in actor.getEnemies():
61 | return False
62 |
63 | if not target.isAlive():
64 | return False
65 |
66 | if not actor.partyCanSee(target.position):
67 | return False
68 |
69 | return True
70 |
71 | def fire(self):
72 | self.caster.world.sounds.get("magic-missile.wav").play()
73 |
74 | def impact(self):
75 | self.caster.world.sounds.get("fireball-impact.wav").play()
76 |
77 | class FireBall(MagicMissile):
78 |
79 | range = 6
80 | damage = 3, 8
81 | heal = False
82 | color = THECOLORS['orange']
83 | name = "Fireball"
84 | icon = "firebolt"
85 |
86 | def fire(self):
87 | self.caster.world.sounds.get("fireball.wav").play()
88 |
89 | def impact(self):
90 | self.caster.world.sounds.get("fireball-impact.wav").play()
91 |
92 |
--------------------------------------------------------------------------------
/src/abilities/chainbolt.py:
--------------------------------------------------------------------------------
1 | from random import randint
2 | from projectiles import Star
3 | from pygame.color import THECOLORS
4 |
5 | class ChainBolt(object):
6 |
7 | range = 5
8 | damage = 2, 5
9 | heal = False
10 | name = "Chain Bolt"
11 | icon = "magicbolt"
12 | cooldown = 4
13 |
14 | def __init__(self, caster, location, item):
15 | self.item = item
16 | self.caster = caster
17 | self.target = location
18 | self.range = self.__class__.range
19 | self.damage = self.__class__.damage
20 | casterName = self.caster.getName()
21 | self.color = THECOLORS['lightcyan']
22 | self.entityHit = self.caster.world.getEntityFromLocation(self.target)
23 | self.bolts = 3
24 | if not self.entityHit is None:
25 | targetName = self.entityHit.getName()
26 | self.caster.world.log.append(casterName + ' cast ' + self.__class__.__name__ + ' at ' + targetName)
27 | else:
28 | self.caster.world.log.append(casterName + ' cast ' + self.__class__.__name__ + ' at nothing!')
29 |
30 | self.projectile = Star(caster.position, location, self.color, self.fire, self.impact)
31 | self.done = False
32 |
33 | def update(self):
34 |
35 | self.projectile.update()
36 | if self.projectile.done:
37 | if self.entityHit is not None:
38 | self.entityHit.inflict(self.caster, randint(self.damage[0], self.damage[1]))
39 | if self.bolts <= 0 or self.entityHit is None:
40 | self.done = True
41 | return True
42 | else:
43 | self.bolts -= 1
44 | nextTarget = self.getAllyInRange(self.entityHit, self.range)
45 | if nextTarget is None:
46 | self.done = True
47 | return True
48 | self.projectile = Star(self.entityHit.position, nextTarget.position, self.color, self.fire, self.impact)
49 | self.entityHit = nextTarget
50 | return False
51 |
52 | def draw(self, surface, position, visible):
53 | if not self.done:
54 | self.projectile.draw(surface, position, visible)
55 |
56 | @classmethod
57 | def validTarget(cls, actor, target):
58 |
59 | if not target in actor.getEnemies():
60 | return False
61 |
62 | if not target.isAlive():
63 | return False
64 |
65 | if not actor.partyCanSee(target.position):
66 | return False
67 |
68 | return True
69 |
70 | def fire(self):
71 | self.caster.world.sounds.get("magic-missile.wav").play()
72 |
73 | def impact(self):
74 | self.caster.world.sounds.get("fireball-impact.wav").play()
75 |
76 | def getAllyInRange(self, entity, radius):
77 |
78 | for e in entity.getFriends():
79 | if e is entity:
80 | continue
81 | if not e.isAlive():
82 | continue
83 | if e.distance(entity.position) < radius:
84 | return e
--------------------------------------------------------------------------------
/src/abilities/projectiles/arrow.py:
--------------------------------------------------------------------------------
1 | '''
2 | Created on 2013-05-28
3 |
4 | @author: brian
5 | '''
6 |
7 | from math import hypot
8 |
9 | from pygame.draw import circle
10 | from collections import deque
11 |
12 | from src.util import Vector2
13 |
14 | class Arrow:
15 |
16 | def __init__(self, origin, endLocation, color = (0xAA, 0x55, 0x00), fire=None, impact=None):
17 |
18 | self.particals = deque()
19 | self.maxParticals = 10
20 |
21 | self.start = Vector2(origin)
22 | self.start.center()
23 | self.pos = Vector2(self.start)
24 | self.end = Vector2(endLocation)
25 | self.end.center()
26 | self.target = Vector2(self.end)
27 | self.target.center()
28 |
29 | self.color = color
30 | self.fire = fire
31 | self.impact = impact
32 |
33 | dx = abs(float(origin[0]) - endLocation[0])
34 | dy = abs(float(origin[1]) - endLocation[1])
35 | dist = int(hypot(dx, dy))
36 | if dist is 0:
37 | dist = 1
38 |
39 | self.vx = (float(self.end[0]) - self.start[0]) / (3 * dist)
40 | self.vy = (float(self.end[1]) - self.start[1]) / (3 * dist)
41 |
42 | self.trail = deque()
43 |
44 | for i in xrange(10):
45 | self.particals.append((self.vx * i * 2, self.vy * i * 2))
46 |
47 | if self.fire is not None:
48 | self.fire()
49 |
50 | self.done = False
51 |
52 | def update(self, pos=None):
53 |
54 | if pos is not None:
55 | self.target = Vector2(pos)
56 | self.target.center()
57 |
58 | tx = abs(float(self.pos[0]) - float(self.start[0]))
59 | ty = abs(float(self.pos[1]) - float(self.start[1]))
60 | distTravel = int(hypot(tx, ty))
61 |
62 | dx = abs(float(self.start[0]) - float(self.target[0]))
63 | dy = abs(float(self.start[1]) - float(self.target[1]))
64 | distTotal = int(hypot(dx, dy))
65 |
66 | if (distTravel >= distTotal):
67 | self.done = True
68 | if self.impact is not None:
69 | self.impact()
70 | return
71 |
72 | self.pos = self.pos[0] + self.vx, self.pos[1] + self.vy
73 |
74 | def draw(self, surface, camPos, visible):
75 |
76 |
77 | tileSize = 32
78 | relx = self.start[0] - camPos[0]
79 | rely = self.start[1] - camPos[1]
80 | center = (((relx + 8) * tileSize), ((rely + 8) * tileSize))
81 |
82 | offsetX = (self.pos[0] - self.start[0]) * tileSize
83 | offsetY = (self.pos[1] - self.start[1]) * tileSize
84 | x = int((center[0] + offsetX))
85 | y = int((center[1] + offsetY))
86 |
87 | pos = (int(self.pos[0]), int(self.pos[1]))
88 |
89 | if not visible(pos):
90 | return
91 |
92 | for partical in self.particals:
93 | px = int(x + partical[0])
94 | py = int(y + partical[1])
95 | circle(surface, self.color, (px, py), 2)
96 |
97 |
98 |
--------------------------------------------------------------------------------
/src/abilities/projectiles/rainbolt.py:
--------------------------------------------------------------------------------
1 | import pygame
2 | import random
3 |
4 | from src.util import Vector2
5 | from math import hypot
6 | from collections import deque
7 | from src.util import Color
8 | from time import time
9 |
10 | class Rainbolt:
11 |
12 | ballSize = 3
13 | scatter = 4
14 | maxParticals = 50
15 | granularity = 15
16 |
17 | def __init__(self, start, end, fire=None, impact=None):
18 | self.start = start
19 | self.end = end
20 |
21 | self.fire = fire
22 | self.impact = impact
23 |
24 | self.particals = deque()
25 |
26 | self.pos = self.origin = Vector2(float(start[0]), float(start[1]))
27 | self.pos.center()
28 | self.target = Vector2(float(end[0]), float(end[1]))
29 | self.target.center()
30 |
31 | dist = self.pos.dist(self.target)
32 | if int(dist) == 0:
33 | dist = 1
34 |
35 | vx = (float(self.target.x) - self.origin.x) / (3 * dist)
36 | vy = (float(self.target.y) - self.origin.y) / (3 * dist)
37 | self.velocity = Vector2(vx, vy)
38 |
39 | if self.fire is not None:
40 | self.fire()
41 |
42 | self.done = False
43 |
44 | def update(self):
45 |
46 | if (int(self.pos.x), int(self.pos.y)) == (int(self.target.x), int(self.target.y)):
47 | self.done = True
48 | if self.impact is not None:
49 | self.impact()
50 | return
51 |
52 | self.pos + self.velocity
53 |
54 | x, y = (self.pos.x, self.pos.y)
55 |
56 |
57 | for i in xrange(5):
58 | s = Rainbolt.scatter
59 | newPartical = (x + float(random.randint(-s,s))/32, y + float(random.randint(-s,s))/32)
60 | self.particals.append(newPartical)
61 |
62 | def draw(self, surface, camPos, visible):
63 |
64 | if self.done:
65 | return
66 |
67 | tileSize = 32
68 | rel = Vector2(self.origin)
69 | rel -= camPos
70 | center = Vector2(rel)
71 | center += Vector2(8, 8)
72 | center *= Vector2(tileSize, tileSize)
73 |
74 | offset = Vector2(self.pos)
75 | offset -= self.origin
76 | offset *= Vector2(tileSize, tileSize)
77 |
78 | vec = Vector2(center)
79 | vec += offset
80 |
81 | x = int(vec[0])
82 | y = int(vec[1])
83 |
84 | if not visible(self.pos):
85 | return
86 |
87 | r = 1.0
88 | color = Color.rainbow(r)
89 | pygame.draw.circle(surface, color, (x, y), Rainbolt.ballSize)
90 |
91 | for i, partical in enumerate(self.particals):
92 | color = Color.rainbow(r + 0.03 * i)
93 | offsetX = (partical[0] - self.origin[0]) * tileSize
94 | offsetY = (partical[1] - self.origin[1]) * tileSize
95 | x = int((center[0] + offsetX))
96 | y = int((center[1] + offsetY))
97 | surface.set_at((x, y), color)
98 | surface.set_at((x, y + 1), color)
99 | surface.set_at((x + 1, y), color)
100 | surface.set_at((x + 1, y + 1), color)
101 |
102 |
103 |
--------------------------------------------------------------------------------
/src/world/terrain/perlin.py:
--------------------------------------------------------------------------------
1 | import random
2 |
3 | class Perlin:
4 |
5 | def __init__(self, nSeed):
6 |
7 | self.mask = 0xFF
8 | self.size = 256
9 |
10 | random.seed(nSeed)
11 |
12 | self.p = range(self.size)
13 | random.shuffle(self.p)
14 | self.gradientX = self.gradientY = [0]*self.size
15 |
16 | for i in range(self.size):
17 | self.gradientX[i] = float(random.randint(0,32767))/(32768/2)-1
18 | self.gradientY[i] = float(random.randint(0,32767))/(32768/2)-1
19 |
20 |
21 | def noise(self, point):
22 |
23 | # find neighbouring grid points
24 | surroundingPoints = self.getSurroundingGridPoints(point)
25 |
26 | # find offsets between point and neighbouring grid-points
27 | offsets = self.getGridOffsets(point)
28 |
29 | # permute values to get pseudo randomly chosen gradients
30 | gradients = self.selectGradients(surroundingPoints)
31 |
32 | # computer the dot product between the vectors and the gradients
33 | dotProducts = self.computeDotProduct(gradients, offsets)
34 |
35 | #modulate with the weight function
36 | weightX = self.weight(offsets[0])
37 | weightY = self.weight(offsets[2])
38 |
39 | v0 = dotProducts[0] - weightX * (dotProducts[0] - dotProducts[1])
40 | v1 = dotProducts[2] - weightX * (dotProducts[2] - dotProducts[3])
41 |
42 | v = v0 - weightY * (v0 - v1)
43 |
44 | return v
45 |
46 | def getSurroundingGridPoints(self, (x, y)):
47 | i0 = int(x)
48 | i1 = i0 + 1
49 |
50 | j0 = int(y)
51 | j1 = j0 + 1
52 |
53 | gpx0 = i0 & self.mask
54 | gpx1 = i1 & self.mask
55 |
56 | gpy0 = j0 & self.mask
57 | gpy1 = j1 & self.mask
58 |
59 | return gpx0, gpx1, gpy0, gpy1
60 |
61 | def getGridOffsets(self, (x, y)):
62 |
63 | i = int(x)
64 | j = int(y)
65 |
66 | tx0 = x - float(i)
67 | tx1 = tx0 - 1
68 |
69 | ty0 = y - float(j)
70 | ty1 = ty0 - 1
71 |
72 | return tx0, tx1, ty0, ty1
73 |
74 |
75 | def selectGradients(self, (qx0, qx1, qy0, qy1)):
76 | q00 = self.p[(qy0 + self.p[qx0]) & self.mask]
77 | q01 = self.p[(qy0 + self.p[qx1]) & self.mask]
78 |
79 | q10 = self.p[(qy1 + self.p[qx0]) & self.mask]
80 | q11 = self.p[(qy1 + self.p[qx1]) & self.mask]
81 |
82 | return q00, q01, q10, q11
83 |
84 | def computeDotProduct(self, (q00, q01, q10, q11), (tx0, tx1, ty0, ty1)):
85 | vec00 = self.gradientX[q00]*tx0 + self.gradientY[q00]*ty0
86 | vec01 = self.gradientX[q01]*tx1 + self.gradientY[q01]*ty0
87 |
88 | vec10 = self.gradientX[q10]*tx0 + self.gradientY[q10]*ty1
89 | vec11 = self.gradientX[q11]*tx1 + self.gradientY[q11]*ty1
90 |
91 | return vec00, vec01, vec10, vec11
92 |
93 | def weight(self, value):
94 | return (3 - (2 * value)) * value * value
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
--------------------------------------------------------------------------------
/src/abilities/projectiles/star.py:
--------------------------------------------------------------------------------
1 | import pygame
2 | import random
3 |
4 | from src.util import Vector2
5 | from math import hypot
6 | from collections import deque
7 |
8 | class Star:
9 |
10 | ballSize = 3
11 | scatter = 8
12 | maxParticals = 20
13 | granularity = 15
14 |
15 | def __init__(self, start, end, color, fire=None, impact=None):
16 | self.start = start
17 | self.end = end
18 |
19 | self.fire = fire
20 | self.impact = impact
21 |
22 | self.particals = deque()
23 |
24 | self.pos = self.origin = Vector2(float(start[0]), float(start[1]))
25 | self.pos.center()
26 | self.target = Vector2(float(end[0]), float(end[1]))
27 | self.target.center()
28 |
29 | dist = self.pos.dist(self.target)
30 | if int(dist) == 0:
31 | dist = 1
32 |
33 | vx = (float(self.target.x) - self.origin.x) / (3 * dist)
34 | vy = (float(self.target.y) - self.origin.y) / (3 * dist)
35 | self.velocity = Vector2(vx, vy)
36 |
37 | if self.fire is not None:
38 | self.fire()
39 |
40 | self.color = color
41 | self.done = False
42 |
43 | def update(self, pos=None):
44 |
45 | if pos is not None:
46 | self.target = Vector2(pos)
47 | self.target.center()
48 | dist = self.pos.dist(self.target)
49 | if int(dist) == 0:
50 | dist = 1
51 |
52 | vx = (float(self.target.x) - self.origin.x) / (3 * dist)
53 | vy = (float(self.target.y) - self.origin.y) / (3 * dist)
54 | self.velocity = Vector2(vx, vy)
55 |
56 | curPos = (int(self.pos[0]), int(self.pos[1]))
57 | endPos = (int(self.target[0]), int(self.target[1]))
58 | if curPos == endPos:
59 | self.done = True
60 | if self.impact is not None:
61 | self.impact()
62 | return
63 |
64 | self.pos + self.velocity
65 |
66 | x, y = (self.pos.x, self.pos.y)
67 |
68 | while len(self.particals) > Star.maxParticals:
69 | self.particals.popleft()
70 |
71 | for i in xrange(3):
72 | s = Star.scatter
73 | newPartical = (x + float(random.randint(-s,s))/32, y + float(random.randint(-s,s))/32)
74 | self.particals.append(newPartical)
75 |
76 | def draw(self, surface, camPos, visible):
77 |
78 | if self.done:
79 | return
80 |
81 | tileSize = 32
82 | relx = self.origin.x - camPos[0]
83 | rely = self.origin.y - camPos[1]
84 | center = (((relx + 8) * tileSize), ((rely + 8) * tileSize))
85 |
86 | offsetX = (self.pos.x - self.origin.x) * tileSize
87 | offsetY = (self.pos.y - self.origin.y) * tileSize
88 | x = int((center[0] + offsetX))
89 | y = int((center[1] + offsetY))
90 |
91 | if not visible(self.pos):
92 | return
93 |
94 | pygame.draw.circle(surface, self.color, (x, y), Star.ballSize)
95 |
96 | for partical in self.particals:
97 | offsetX = (partical[0] - self.origin[0]) * tileSize
98 | offsetY = (partical[1] - self.origin[1]) * tileSize
99 | x = int((center[0] + offsetX))
100 | y = int((center[1] + offsetY))
101 | surface.set_at((x, y), self.color)
102 | surface.set_at((x, y + 1), self.color)
103 | surface.set_at((x + 1, y), self.color)
104 | surface.set_at((x + 1, y + 1), self.color)
105 |
106 |
107 |
--------------------------------------------------------------------------------
/src/world/terrain/chunkgen.py:
--------------------------------------------------------------------------------
1 | from simplex import Simplex
2 | import terrain
3 | import random
4 | import tile
5 |
6 | # This class generates new chunks on request
7 | class ChunkGen:
8 |
9 | def __init__(self, seed, inChunkPos):
10 | self.chunkPos = inChunkPos
11 | random.seed(seed)
12 | self.noiseGen = Simplex(seed)
13 | self.chunkSize = 16
14 |
15 | self.mats = {}
16 | self.mats[-10] = terrain.Water.id
17 | self.mats[-9] = terrain.Water.id
18 | self.mats[-8] = terrain.Water.id
19 | self.mats[-7] = terrain.Water.id
20 | self.mats[-6] = terrain.Water.id
21 | self.mats[-5] = terrain.Water.id
22 | self.mats[-4] = terrain.Water.id
23 | self.mats[-3] = terrain.Water.id
24 | self.mats[-2] = terrain.Water.id
25 | self.mats[-1] = terrain.Water.id
26 | self.mats[0] = terrain.Water.id
27 | self.mats[1] = terrain.Sand.id
28 | self.mats[2] = terrain.Grass.id
29 | self.mats[3] = terrain.Grass.id
30 | self.mats[4] = terrain.Brush.id
31 | self.mats[5] = terrain.Foothills.id
32 | self.mats[6] = terrain.Mountain.id
33 | self.mats[7] = terrain.Mountain.id
34 | self.mats[8] = terrain.Peak.id
35 | self.mats[9] = terrain.Peak.id
36 | self.mats[10] = terrain.Peak.id
37 |
38 | # Call this when you want a new chunk
39 | def generate(self):
40 |
41 | # create blank chunk
42 | newChunk = [tile.Tile() for i in range(self.chunkSize ** 2)]
43 |
44 | # fill in the basic terrain
45 | newChunk = self.genTerrain(newChunk)
46 |
47 | # add decorations, like trees
48 | newChunk = self.decorate(newChunk)
49 |
50 | return newChunk
51 |
52 | def genTerrain(self, newChunk):
53 |
54 | octaves = 6
55 | persistence = 0.75
56 | scale = 0.004
57 |
58 | for row in range(self.chunkSize):
59 | for col in range(self.chunkSize):
60 |
61 | xPos = (self.chunkPos[0]*self.chunkSize + col)
62 | yPos = (self.chunkPos[1]*self.chunkSize + row)
63 |
64 | elevation = self.noiseGen.octave_noise_2d(octaves, persistence, scale, xPos, yPos)
65 |
66 | elevation *= 15
67 |
68 | elevation = int(elevation)
69 |
70 | ground = self.getGroundMat(elevation)
71 |
72 | newChunk[row*self.chunkSize + col].setGround(ground)
73 |
74 | return newChunk
75 |
76 |
77 |
78 | # takes an integer argument (0-9)
79 | # returns a tile
80 | def getGroundMat(self, elevation):
81 |
82 | if (elevation < -10):
83 | elevation = -10
84 |
85 | if (elevation > 9):
86 | elevation = 9
87 |
88 | newMat = self.mats[elevation]
89 |
90 | return newMat
91 |
92 | def decorate(self, newChunk):
93 |
94 | # add some features
95 | for i in range(self.chunkSize ** 2):
96 |
97 | if(newChunk[i].getGround() in [terrain.Grass, terrain.Brush]):
98 |
99 | # add trees and stuff
100 | if(random.randint(0,10) == 0):
101 |
102 | if random.randint(0, 4) == 0:
103 | newChunk[i].build(terrain.DeadTree.id)
104 | else:
105 | newChunk[i].build(terrain.Tree.id)
106 |
107 | if(newChunk[i].getGround() == terrain.Foothills):
108 |
109 | if(random.randint(0,10) == 0):
110 | newChunk[i].build(terrain.Rock.id)
111 |
112 | return newChunk
--------------------------------------------------------------------------------
/src/entity/party.py:
--------------------------------------------------------------------------------
1 | '''
2 | Created on 2013-05-26
3 |
4 | @author: brian
5 | '''
6 |
7 | from mobs import Bard
8 | from mobs import Fighter
9 | from mobs import Mage
10 | from mobs import Priest
11 |
12 | class Party(object):
13 |
14 | def __init__(self, world):
15 |
16 | self.world = world
17 | self.members = []
18 | self.leader = 0
19 | self.visibilityCache = {}
20 | self.visibilityTurn = world.time
21 |
22 | def add(self, entity):
23 | self.members.append(entity)
24 | entity.setGroup(self)
25 |
26 | def save(self):
27 |
28 | data = {}
29 |
30 | members = []
31 |
32 | for e in self.members:
33 | members.append(e.save())
34 |
35 | data['members'] = members
36 | data['leader'] = self.leader
37 |
38 | return data
39 |
40 | def setLeader(self, index):
41 |
42 | if not index in range(len(self.members)):
43 | return
44 |
45 | self.members[self.leader].ai.active = True
46 | self.leader = index
47 | self.members[self.leader].ai.active = False
48 |
49 | return self.getLeader()
50 |
51 | def load(self, data):
52 |
53 | if data is None:
54 |
55 | member = Fighter(self.world)
56 | member.equip()
57 | member.name = "Steve"
58 | self.members.append(member)
59 | member.setGroup(self)
60 |
61 | member = Fighter(self.world)
62 | member.equip()
63 | member.name = "Gregg"
64 | self.members.append(member)
65 | member.setGroup(self)
66 |
67 | member = Mage(self.world)
68 | member.equip()
69 | member.name = "Johne"
70 | self.members.append(member)
71 | member.setGroup(self)
72 |
73 | member = Priest(self.world)
74 | member.equip()
75 | member.name = "Maya"
76 | self.members.append(member)
77 | member.setGroup(self)
78 |
79 | member = Priest(self.world)
80 | member.equip()
81 | member.name = "Alice"
82 | self.members.append(member)
83 | member.setGroup(self)
84 |
85 | member = Bard(self.world)
86 | member.equip()
87 | member.name = "Kat"
88 | self.members.append(member)
89 | member.setGroup(self)
90 | member.position = self.world.getSpawnLocation()
91 |
92 | self.setLeader(0)
93 |
94 | for e in self.members:
95 | e.teleportToLeader()
96 |
97 | return
98 |
99 | for e in data['members']:
100 | member = self.world.mobManager.loadEntity(e)
101 | self.members.append(member)
102 | member.setGroup(self)
103 |
104 | self.setLeader(data['leader'])
105 |
106 | def getLeader(self):
107 | return self.members[self.leader]
108 |
109 |
110 | def canSee(self, position):
111 |
112 | pos = (position[0], position[1])
113 |
114 | if self.visibilityTurn is not self.world.time:
115 | self.visibilityTurn = self.world.time
116 | self.visibilityCache = {}
117 |
118 | if pos in self.visibilityCache:
119 | return self.visibilityCache[pos]
120 |
121 | for e in self.members:
122 | if e.canSee(position):
123 | self.visibilityCache[pos] = True
124 | return True
125 |
126 | self.visibilityCache[pos] = False
127 | return False
128 |
129 | def resetLeader(self):
130 | for i, e in enumerate(self.members):
131 | if e.isAlive():
132 | self.setLeader(i)
133 |
134 | def __iter__(self):
135 | return iter(self.members)
136 |
--------------------------------------------------------------------------------
/src/gui/viewport.py:
--------------------------------------------------------------------------------
1 | '''
2 | Created on 2013-05-23
3 |
4 | @author: brian
5 | '''
6 |
7 | import pygame
8 | from pygame.color import THECOLORS
9 | from src.util import Vector2
10 | from cell import Cell
11 |
12 | class Viewport(object):
13 |
14 | size = 17
15 |
16 | def __init__(self, surface, pos, world, player, images):
17 | self.images = images
18 | self.surface = surface
19 | self.pos = pos
20 | self.world = world
21 | self.player = player
22 | self.reticule = pygame.transform.scale2x(images.get("reticule"))
23 | self.grid = {}
24 |
25 | for x in range(Viewport.size):
26 | for y in range(Viewport.size):
27 | pos = Vector2(x * Cell.size, y * Cell.size)
28 | rel = Vector2(x - Viewport.size / 2, y - Viewport.size / 2)
29 | cell = Cell(pos, rel)
30 | cell.observers.append(player.targetcontrol)
31 | self.grid[(rel[0], rel[1])] = cell
32 |
33 | def display(self, info):
34 | self.fontobject = pygame.font.Font(None,24)
35 | for i, line in enumerate(info):
36 | self.surface.blit(self.fontobject.render(line[0], 1, (255,255,255)), (Cell.size / 2, (i + 1) * Cell.size / 2))
37 |
38 | def draw(self):
39 | if(self.player.viewingMap):
40 | self.drawMap()
41 | return
42 |
43 | self.drawGame()
44 |
45 |
46 | def drawGame(self):
47 | self.surface.fill(THECOLORS["black"])
48 | camPos = self.player.party.getLeader().position
49 |
50 | for cell in self.grid.values():
51 | loc = Vector2(camPos)
52 | loc += cell.rel
53 | if not self.player.party.canSee(loc):
54 | continue
55 | tile = self.world.getTile(loc)
56 | ground = tile.getGround()
57 | imgName = ground.getImage(loc)
58 | image = self.images.get(imgName, loc)
59 | self.surface.blit(image, (int(cell.pos[0]), int(cell.pos[1])))
60 |
61 | for e in self.world.getAllEntities():
62 | e.draw(self.surface, camPos, self.images, self.visible)
63 |
64 | if self.player.action is not None:
65 | if hasattr(self.player.action, 'location'):
66 | if self.player.action.location is not None:
67 | '''
68 | x = 32 * (8 + self.player.action.location[0])
69 | y = 32 * (8 + self.player.action.location[1])
70 | '''
71 | cell = self.grid[self.player.action.location]
72 | self.surface.blit(self.reticule, cell.pos)
73 |
74 | if self.player.reticle is not None and self.player.action is not None:
75 | r = Vector2(self.player.reticle)
76 | r += (8, 8)
77 | self.surface.blit(self.reticule, (r[0] * 32, r[1] * 32))
78 |
79 |
80 | def drawMap(self):
81 | self.surface.fill(THECOLORS["wheat4"])
82 |
83 | x, y = self.player.party.getLeader().position
84 | x, y = (int(x) >> 4) - 8, (int(y) >> 4) - 8
85 |
86 | for row in range(17):
87 | for col in range(17):
88 | img = self.world.chunkManager.getMap(x + col, y + row)
89 | if img is None:
90 | continue
91 |
92 | dest = (col * 32), (row * 32)
93 | self.surface.blit(img, dest)
94 |
95 | for mob in self.world.mobManager.mobs:
96 | self.map_dot(mob.position, THECOLORS["red"])
97 |
98 | for e in self.world.friendly:
99 | self.map_dot(e.position, THECOLORS["azure"])
100 |
101 | leader = self.player.party.getLeader()
102 | self.map_dot(leader.position, THECOLORS["yellow"])
103 |
104 | def visible(self, pos):
105 | return self.player.party.canSee(pos)
106 |
107 | def notify(self, pos, event):
108 | vec = Vector2(pos)
109 | vec -= self.pos
110 | rel = (int(vec[0] / 32) - 8, int(vec[1] / 32) - 8)
111 | self.grid[rel].notify(event)
112 |
113 | def map_dot(self, pos, color):
114 | x, y = self.player.party.getLeader().position
115 | origin = (int(x) >> 4) - 8, (int(y) >> 4) - 8
116 | cpos = int(pos[0]) >> 4, int(pos[1]) >> 4
117 | posx = int(((cpos[0] - origin[0]) * 32) + ((pos[0] % 16) * 2))
118 | posy = int(((cpos[1] - origin[1]) * 32) + ((pos[1] % 16) * 2))
119 |
120 | self.surface.set_at((posx, posy), color)
121 | self.surface.set_at((posx, posy + 1), color)
122 | self.surface.set_at((posx + 1, posy), color)
123 | self.surface.set_at((posx + 1, posy + 1), color)
124 |
125 |
--------------------------------------------------------------------------------
/src/abilities/explosion.py:
--------------------------------------------------------------------------------
1 | from random import randint
2 | from projectiles import Star
3 | from math import sqrt
4 | from pygame.color import THECOLORS
5 |
6 | class Explosion(object):
7 |
8 | range = 6
9 | radius = 4
10 | damage = 2, 5
11 | heal = False
12 | name = "Explosion"
13 | icon = "firebolt"
14 | cooldown = 7
15 |
16 | def __init__(self, caster, location, item):
17 | self.item = item
18 | self.range = Explosion.range
19 | self.caster = caster
20 | self.target = location
21 | self.range = self.__class__.range
22 | self.damage = self.__class__.damage
23 | self.color = THECOLORS['orange']
24 | casterName = self.caster.getName()
25 | self.entityHit = self.caster.world.getEntityFromLocation(self.target)
26 | if not self.entityHit is None:
27 | targetName = self.entityHit.getName()
28 | self.caster.world.log.append(casterName + ' cast ' + self.__class__.__name__ + ' at ' + targetName)
29 | else:
30 | self.caster.world.log.append(casterName + ' cast ' + self.__class__.__name__ + ' at nothing!')
31 |
32 | self.projectile = Star(caster.position, location, self.color, self.fire, self.impact)
33 | self.explosion = []
34 | self.done = False
35 |
36 | def update(self):
37 |
38 | if not self.projectile.done:
39 | self.projectile.update()
40 |
41 | if self.projectile.done:
42 | #explosion start
43 | targets = Explosion.getNearbyTiles(self.target, Explosion.radius)
44 | if not targets:
45 | self.done = True
46 | return True
47 | for pos in targets:
48 | newStar = Star(self.target, pos, self.color, None, None)
49 | self.explosion.append(newStar)
50 | return False
51 |
52 | if not self.explosion:
53 | return True
54 |
55 | done = True
56 | for star in self.explosion:
57 | if star.done:
58 | continue
59 | star.update()
60 | if star.done:
61 | e = self.caster.world.getEntityFromLocation(star.end)
62 | if e is not None:
63 | e.inflict(self.caster, randint(self.damage[0], self.damage[1]))
64 | else:
65 | done = False
66 |
67 | if done:
68 | self.done = True
69 |
70 | return done
71 |
72 | def draw(self, surface, position, visible):
73 | if not self.done:
74 | if not self.projectile.done:
75 | self.projectile.draw(surface, position, visible)
76 | if self.explosion:
77 | for star in self.explosion:
78 | star.draw(surface, position, visible)
79 |
80 | @staticmethod
81 | def getNearbyTiles(pos, r):
82 | targets = []
83 | xP = pos[0] - (r - 1)
84 | yP = pos[1] - (r - 1)
85 | for x in xrange(r * 2 - 1):
86 | for y in xrange(r * 2 - 1):
87 | toHit = (xP + x, yP + y)
88 | if Explosion.canHit(pos, toHit, r):
89 | targets.append(toHit)
90 | return targets
91 |
92 | @staticmethod
93 | def canHit(origin, position, r):
94 |
95 | relx = abs(float(origin[0]) - float(position[0]))
96 | rely = abs(float(origin[1]) - float(position[1]))
97 |
98 | distance = sqrt(relx**2 + rely**2)
99 |
100 | if(distance <= r - 1):
101 | return True
102 |
103 | return False
104 |
105 | @classmethod
106 | def validTarget(cls, actor, target):
107 |
108 | if not target in actor.getEnemies():
109 | return False
110 |
111 | if not target.isAlive():
112 | return False
113 |
114 | if not actor.partyCanSee(target.position):
115 | return False
116 |
117 | nearbyTiles = cls.getNearbyTiles(target.position, Explosion.radius)
118 | for pos in nearbyTiles:
119 | e = actor.world.getEntityFromLocation(pos)
120 | if e is None:
121 | continue
122 | if not target.isAlive():
123 | continue
124 | if e is actor:
125 | return False
126 | if e in actor.getFriends():
127 | return False
128 |
129 | return True
130 |
131 | def fire(self):
132 | self.caster.world.sounds.get("fireball.wav").play()
133 |
134 | def impact(self):
135 | self.caster.world.sounds.get("explosion.wav").play()
136 |
137 | def getAllyInRange(self, entity, radius):
138 |
139 | for e in entity.getFriends():
140 | if e is entity:
141 | continue
142 | if not e.isAlive():
143 | continue
144 | if e.distance(entity.position) < radius:
145 | return e
--------------------------------------------------------------------------------
/src/world/world.py:
--------------------------------------------------------------------------------
1 | import sys
2 | from random import randint
3 | import math
4 | import time
5 | import os
6 | from src.items import ItemFactory
7 |
8 | from src.util import Vector2
9 | from src.util import Line
10 |
11 | from src.entity import MobManager
12 | from src.entity import Party
13 | from terrain import ChunkManager
14 | import terrain
15 |
16 | class World:
17 |
18 | def __init__(self, seed, sounds):
19 |
20 | if not os.path.isdir('save'):
21 | os.mkdir('save')
22 |
23 | self.sounds = sounds
24 | self.items = ItemFactory(terrain)
25 | self.combat = 0
26 | self.seed = seed
27 | self.chunkManager = ChunkManager(self)
28 | self.time = 0
29 | self.mobManager = MobManager(self)
30 | self.friendly = None
31 | self.spawn = self.getSpawnLocation()
32 | self.log = None
33 | self.materials = terrain.lookup
34 | self.entityCache = None
35 |
36 |
37 | def getSeed(self):
38 | return self.seed
39 |
40 | def update(self):
41 |
42 | done = True
43 | for e in list(set(self.friendly) | set(self.mobManager.mobs)):
44 | if e.update() is False:
45 | done = False
46 | return done
47 |
48 |
49 | def turn(self):
50 |
51 | self.mobManager.turn()
52 | for e in self.friendly:
53 | e.turn()
54 |
55 | self.entityCache = None
56 |
57 | leader = self.friendly.getLeader()
58 | pos = int(leader.position[0]) >> 4, int(leader.position[1]) >> 4
59 | self.chunkManager.cull(pos, 3)
60 |
61 | self.time += 1
62 |
63 | def isLocationPassable(self, location):
64 |
65 | entity = self.getEntityFromLocation(location)
66 | if entity is not None and entity.isAlive():
67 | return False
68 |
69 | tile = self.chunkManager.getTile(location)
70 | if tile is None:
71 | return False
72 |
73 | return tile.isPassable()
74 |
75 | def look(self, position):
76 |
77 | for e in list(set(self.mobManager.mobs) | set(self.friendly)):
78 | if e.position == position:
79 | if e.name is not None:
80 | return e.name
81 | return e.singular
82 |
83 | env = self.chunkManager
84 |
85 | x, y = position
86 | t = env.getTile((x, y))
87 | m = t.getGround()
88 | return m.singular
89 |
90 | def getTile(self, location):
91 | return self.chunkManager.getTile(location)
92 |
93 | def setTile(self, location, tileId):
94 | self.chunkManager.setTile(location, tileId)
95 | return True
96 |
97 | def build(self, location, tileId):
98 | tile = self.getTile(location)
99 | if not tile.canBuild():
100 | return False
101 | self.chunkManager.setTile(location, tileId)
102 | return True
103 |
104 | def destroy(self, location):
105 | tile = self.getTile(location)
106 | return tile.destroy()
107 |
108 | def quit(self):
109 | self.chunkManager.saveChunks()
110 | sys.exit()
111 |
112 | def getSpawnLocation(self):
113 | x = 0
114 | y = 0
115 |
116 | spread = 1
117 |
118 | while(not (self.getTile((x, y)).getGround().spawnable)):
119 | x = randint(-spread,spread)
120 | y = randint(-spread,spread)
121 | if spread is 100:
122 | print "Failed to find a place to spawn"
123 | break
124 | spread += 1
125 |
126 | return x, y
127 |
128 | def getEntityFromLocation(self, location, living = True):
129 | for e in self.getAllEntities():
130 | if e.position == location:
131 | if living and e.isAlive():
132 | return e
133 | elif not living and not e.isAlive():
134 | return e
135 |
136 | return None
137 |
138 | def getAllEntities(self):
139 | if self.friendly is None:
140 | return self.mobManager.mobs
141 |
142 | if self.entityCache is None:
143 | self.entityCache = list(set(self.mobManager.mobs) | set(self.friendly.members))
144 | self.entityCache.sort(lambda a, b: cmp(a.isAlive(), b.isAlive()))
145 | return self.entityCache
146 |
147 | def obscured(self, start, end):
148 |
149 | vStart = Vector2(start[0], start[1])
150 | vStart.center()
151 | vEnd = Vector2(end[0], end[1])
152 | vEnd.center()
153 | ray = Line(vStart, vEnd)
154 |
155 | for vec in ray:
156 | pos = (int(math.floor(vec.x)), int(math.floor(vec.y)))
157 | tile = self.getTile(pos)
158 | if not tile.isTransparent():
159 | return True
160 |
161 | return False
162 |
163 | def loadParty(self, data):
164 | party = Party(self)
165 | party.load(data)
166 | return party
167 |
168 |
--------------------------------------------------------------------------------
/src/world/terrain/terrain.py:
--------------------------------------------------------------------------------
1 |
2 | from random import randint
3 | from random import Random
4 |
5 | # Base material class
6 | class Material:
7 |
8 | id = 0
9 | singular = 'nothing'
10 | passable = True
11 | transparent = True
12 | breakable = False
13 | image = "tile"
14 | rgb = (20, 20, 20)
15 | jitter = 10
16 | spawnable = False
17 | step = "step.wav"
18 |
19 | @classmethod
20 | def color(cls):
21 | return gritify(cls.rgb, cls.jitter)
22 |
23 | @classmethod
24 | def getImage(cls, x=0, y=0):
25 | return cls.image
26 |
27 | @classmethod
28 | def drop(cls):
29 | return cls.id
30 |
31 | @classmethod
32 | def stepSound(cls, sounds):
33 | sounds.get(cls.step).play()
34 |
35 | ### Terrain materials below ###
36 |
37 | class Void(Material):
38 |
39 | singular = 'the void'
40 | symbol = '#'
41 | passable = False
42 | image = "tile"
43 |
44 | class Grass(Material):
45 |
46 | id = 1
47 | singular = 'grass'
48 | symbol = '-'
49 | image = "grass"
50 | step = "step-grass.wav"
51 | rgb = (0, 200, 0)
52 | spawnable = True
53 |
54 | class Brush(Material):
55 |
56 | id = 2
57 | singular = 'brush'
58 | symbol = 's'
59 | image = "brush"
60 | rgb = (90, 150, 50)
61 | spawnable = True
62 | step = "step-brush.wav"
63 |
64 | class FloorBrick(Material):
65 |
66 | id = 3
67 | singular = 'floor'
68 | symbol = 'x'
69 | image = "floor-brick"
70 | breakable = True
71 | rgb = (150, 50, 50)
72 |
73 |
74 | class Foothills(Material):
75 |
76 | id = 4
77 | singular = 'foothills'
78 | symbol = 'n'
79 | image = "foothills"
80 | rgb = (180, 160, 140)
81 | spawnable = True
82 |
83 |
84 | class WallBrick(Material):
85 |
86 | id = 5
87 | singular = 'stone brick'
88 | symbol = 'H'
89 | image = "wall-brick"
90 | passable = False
91 | transparent = False
92 |
93 | class Mountain(Material):
94 |
95 | id = 6
96 | singular = 'a mountain'
97 | symbol = '^'
98 | image = "mountain"
99 | passable = False
100 | transparent = False
101 | rgb = (230, 230, 230)
102 | r = 50
103 |
104 | class Peak(Material):
105 |
106 | id = 7
107 | singular = 'a high peak'
108 | symbol = 'A'
109 | image = "peaks"
110 | passable = False
111 | transparent = False
112 | rgb = (255, 255, 255)
113 |
114 |
115 | class Water(Material):
116 |
117 | id = 8
118 | singular = 'still water'
119 | symbol = '~'
120 | image = "water"
121 | passable = False
122 | rgb = (100, 100, 255)
123 | r = 60
124 |
125 | class WallStone(Material):
126 | id = 9
127 | singular = 'cobblestone'
128 | symbol = '&'
129 | image = "wall-stone"
130 | passable = False
131 | breakable = True
132 | transparent = False
133 | rgb = (150, 150, 150)
134 |
135 |
136 | class Tree(Material):
137 | id = 10
138 | singular = 'a tree'
139 | symbol = '@'
140 | image = "tree"
141 | passable = False
142 | breakable = True
143 | transparent = False
144 | rgb = (120, 75, 0)
145 |
146 | @classmethod
147 | def drop(cls):
148 | return Plank.id
149 |
150 |
151 | class Well(Material):
152 | id = 11
153 | singular = 'a well'
154 | symbol = 'T'
155 | image = "well"
156 | passable = False
157 |
158 | class Door(Material):
159 | id = 12
160 | singular = 'a door'
161 | symbol = 'O'
162 | image = "door"
163 | passable = False
164 | breakable = True
165 | transparent = False
166 |
167 | class Rock(Material):
168 | id = 13
169 | singular = 'a pile of rocks'
170 | symbol = 'R'
171 | image = "rocks"
172 | passable = False
173 | breakable = True
174 | transparent = False
175 | rgb = (80, 80, 80)
176 |
177 | @classmethod
178 | def drop(cls):
179 | return WallStone.id
180 |
181 | class DeadTree(Material):
182 |
183 | id = 14
184 | singular = 'a dead tree'
185 | symbol = '@'
186 | image = "tree-dead"
187 | passable = False
188 | breakable = True
189 | rgb = (120, 75, 0)
190 |
191 | @classmethod
192 | def drop(cls):
193 | return Plank.id
194 |
195 | class Sand(Material):
196 | id = 15
197 | singular = 'sand'
198 | symbol = 'R'
199 | image = "sand"
200 | step = "step-sand.wav"
201 | passable = True
202 | breakable = False
203 | rgb = (255, 210, 120)
204 | spawnable = True
205 |
206 | class Plank(Material):
207 |
208 | id = 16
209 | singular = 'plank'
210 | symbol = 'H'
211 | image = "planks"
212 | step = "step-wood.wav"
213 | breakable = True
214 | rgb = (120, 75, 0)
215 |
216 | lookup = {}
217 | lookup[Void.id] = Void
218 | lookup[Grass.id] = Grass
219 | lookup[Brush.id] = Brush
220 | lookup[FloorBrick.id] = FloorBrick
221 | lookup[Foothills.id] = Foothills
222 | lookup[WallBrick.id] = WallBrick
223 | lookup[Mountain.id] = Mountain
224 | lookup[Peak.id] = Peak
225 | lookup[Water.id] = Water
226 | lookup[WallStone.id] = WallStone
227 | lookup[Tree.id] = Tree
228 | lookup[Well.id] = Well
229 | lookup[Door.id] = Door
230 | lookup[Rock.id] = Rock
231 | lookup[DeadTree.id] = DeadTree
232 | lookup[Sand.id] = Sand
233 | lookup[Plank.id] = Plank
234 |
235 | def gritify(color, jitter):
236 |
237 | rgb = [0, 0, 0]
238 |
239 | for i in range(3):
240 | c = color[i] + randint(-jitter, jitter)
241 | if c < 0:
242 | c = 0
243 |
244 | if c > 255:
245 | c = 255
246 |
247 | rgb[i] = c
248 |
249 | newColor = (rgb[0], rgb[1], rgb[2])
250 | return newColor
251 |
--------------------------------------------------------------------------------
/src/player/player.py:
--------------------------------------------------------------------------------
1 | import time
2 | import sys
3 | import os
4 | import json
5 |
6 | from src.util import Cardinal
7 | from src.actions import lookup
8 | from src.actions import Quit
9 | from src.actions import Cast
10 | from src.util import Vector2
11 | from entitycontrol import EntityControl
12 | from targetcontrol import TargetControl
13 | from abilitycontrol import AbilityControl
14 |
15 | import pygame
16 | from pygame.locals import *
17 | from pygame.color import THECOLORS
18 |
19 | class Player:
20 |
21 | ABILITY_KEYS = [K_q, K_w, K_e, K_r]
22 |
23 | def __init__(self, game):
24 | self.game = game
25 | world = game.world
26 | self.world = world
27 |
28 | self.log = ['Welcome']
29 | self.world.log = self.log
30 |
31 | self.entitycontrol = EntityControl(self)
32 | self.targetcontrol = TargetControl(self)
33 | self.abilitycontrol = AbilityControl(self)
34 | self.lastAction = 0
35 | self.lastTurn = 0
36 | self.load() #party
37 | self.reticle = (0, 0)
38 | self.target = None
39 | for entity in self.party:
40 | entity.observers.append(self.entitycontrol)
41 | for ability in entity.abilities:
42 | ability.observers.append(self.abilitycontrol)
43 | self.world.friendly = self.party
44 | self.lastTarget = None
45 |
46 | self.avatar = self.party.getLeader()
47 | self.viewingMap = False
48 | self.debug = False
49 | self.action = None
50 | self.turnDelay = 0.3
51 |
52 | pygame.key.set_repeat()
53 |
54 | self.screenshot = None #initialized by the GameView class
55 |
56 | # Called on every frame
57 | # Return true if the player hasn't used his or her turn yet
58 | # Return false to allow the rest of the world to take a turn
59 | def turn(self):
60 |
61 | #if self.lastTurn is self.world.time:
62 | # return False
63 |
64 | if self.action is not None:
65 | finished = self.action.nextStep()
66 | if(finished):
67 | self.action = None
68 | self.target = None
69 | self.lastAction = time.time()
70 | return False
71 | else:
72 | return True
73 |
74 | if not self.party.getLeader().isAlive():
75 | leader = self.party.resetLeader()
76 | self.avatar = leader
77 | self.log.append(leader.name + " is now the leader.")
78 |
79 | events = pygame.event.get()
80 |
81 | mods = pygame.key.get_mods()
82 |
83 | for e in events:
84 | if(e.type == QUIT):
85 | self.world.quit()
86 | elif(e.type == KEYDOWN):
87 |
88 | if e.key == K_m:
89 | self.viewingMap = not self.viewingMap
90 |
91 | if e.key == K_F2:
92 | self.screenshot()
93 |
94 | if e.key == K_F3:
95 | self.debug = not self.debug
96 |
97 | if e.key == K_F12:
98 | self.action = Quit(self)
99 |
100 | if e.key in lookup:
101 | self.action = lookup[e.key](self)
102 | return True
103 |
104 | for i, k in enumerate(Player.ABILITY_KEYS):
105 | if e.key == k and len(self.avatar.abilities) > i:
106 | spell = self.avatar.abilities[i]
107 | if not spell.ready():
108 | self.log.append('Ability on cooldown!')
109 | return True
110 | self.action = Cast(self, spell)
111 |
112 | # select unit to control (deliberately off by one)
113 | if(e.key - 49 in range(len(self.party.members))):
114 | self.setLeader(self.party.members[e.key - 49])
115 |
116 | elif(e.type == KEYUP):
117 | pass
118 |
119 |
120 | elif e.type == pygame.MOUSEBUTTONUP or e.type == pygame.MOUSEMOTION:
121 | last = self.lastAction
122 | self.mouse_event(e)
123 | if last != self.lastAction:
124 | # A turn was completed via a mouse
125 | return False
126 |
127 |
128 | if(time.time() - self.lastAction > self.turnDelay):
129 |
130 | pygame.event.pump()
131 | pressed = pygame.key.get_pressed()
132 | result = ''
133 |
134 | for direction in Cardinal.key_map.keys():
135 | if pressed[direction]:
136 | self.move(Cardinal.key_map[direction])
137 | return False
138 |
139 | if pressed[K_SPACE]:
140 | self.log.append('Pass Turn')
141 | self.lastAction = time.time()
142 | self.lastTurn = self.world.time
143 | return False
144 |
145 | return True
146 |
147 | def move(self, direction):
148 | e = self.party.getLeader()
149 | newPos = Vector2(e.position)
150 | newPos += Vector2(Cardinal.values[direction])
151 | succeeded = e.move(Cardinal.values[direction])
152 | if succeeded:
153 | msg = Cardinal.names[direction]
154 | self.world.getTile(newPos).getGround().stepSound(self.world.sounds)
155 | else:
156 | msg = "Blocked by " + self.world.look(newPos)
157 | self.world.sounds.get("oomph.wav").play()
158 | self.log.append(msg)
159 | self.lastAction = time.time()
160 | self.lastTurn = self.world.time
161 |
162 | def setLeader(self, entity):
163 | if not entity in self.party:
164 | return
165 |
166 | i = self.party.members.index(entity)
167 | self.avatar = self.party.setLeader(i)
168 |
169 | def save(self):
170 | data = {}
171 | data['members'] = self.party.save()
172 |
173 | with open('save/player', 'w') as f:
174 | json.dump(data, f, sort_keys=True, indent=4)
175 |
176 | def load(self):
177 |
178 | if not os.path.isfile('save/player'):
179 | self.party = self.world.loadParty(None)
180 | return
181 |
182 | with open('save/player', 'r') as f:
183 | data = json.load(f)
184 |
185 | if 'members' in data.keys():
186 | self.party = self.world.loadParty(data['members'])
187 | else:
188 | self.party = self.world.loadParty(None)
189 |
190 | def mouse_event(self, event):
191 | self.game.view.notify(pygame.mouse.get_pos(), event)
192 |
--------------------------------------------------------------------------------
/src/entity/mobs/entity.py:
--------------------------------------------------------------------------------
1 | import pygame
2 | from math import sqrt
3 | from math import floor
4 | from random import randint
5 |
6 | from src.ai import AIController
7 | from src.inventory import Inventory
8 | from src.util import Vector2
9 |
10 | from time import time
11 | from src.abilities import Ability
12 |
13 |
14 | class Entity:
15 |
16 | living = "player"
17 | dead = "body"
18 | damage = "damage"
19 | step = "step.wav"
20 | ouch = "ouch.wav"
21 |
22 | def __init__(self, world):
23 | self.image = None
24 | self.gore = None
25 | self.position = Vector2(0,0)
26 | self.world = world
27 | self.group = None
28 | self.ai = AIController()
29 | self.abilities = [Ability(self, Ability.lookup["Attack"])]
30 | self.singular = 'unknown'
31 | self.sight = 5
32 | self.health = self.maxHealth = 30
33 | self.inventory = Inventory(self, world.items)
34 | self.deathTimer = 50
35 | self.lastDamage = 0
36 | self.hostile = False
37 | self.lastAttacker = None
38 | self.name = None
39 | self.action = None
40 | self.lastTarget = None
41 | self.observers = []
42 | self.quickcast = None
43 |
44 | def move(self, direction):
45 |
46 | x, y = direction
47 | oldx, oldy = self.position
48 |
49 | newx = floor(oldx + x)
50 | newy = floor(oldy + y)
51 |
52 | if not self.world.isLocationPassable(Vector2(newx, newy)):
53 | return False
54 |
55 | self.position = Vector2(newx, newy)
56 |
57 | return True
58 |
59 | def update(self):
60 |
61 | done = True
62 |
63 | if self.action is not None:
64 | done = self.action.update()
65 | if done:
66 | self.action = None
67 |
68 | return done
69 |
70 |
71 |
72 | def draw(self, screen, camPos, images, visible):
73 |
74 | tileSize = 32
75 | relx = self.position[0] - camPos[0]
76 | rely = self.position[1] - camPos[1]
77 | position = (((relx + 8)*tileSize), ((rely + 8) * tileSize))
78 |
79 | if visible(self.position):
80 | screen.blit(self.getImage(images), position)
81 |
82 | if self.action is not None:
83 | self.action.draw(screen, camPos, visible)
84 |
85 | def getImage(self, images):
86 | if self.health == 0:
87 | return images.get(self.__class__.dead, self.position)
88 | elif time() - self.lastDamage < 0.2:
89 | return images.get(self.__class__.damage, self.position)
90 | else:
91 | return images.get(self.__class__.living, self.position)
92 |
93 | def save(self):
94 | data = {}
95 | data['type'] = self.__class__.__name__
96 | data['position'] = Vector2.save(self.position)
97 | data['health'] = self.health
98 | data['deathTimer'] = self.deathTimer
99 | data['hostile'] = self.hostile
100 | data['name'] = self.name
101 | data['inventory'] = self.inventory.save()
102 | abi = []
103 | for ability in self.abilities:
104 | abi.append(ability.save())
105 | data['abilities'] = abi
106 | data['ai'] = self.ai.save()
107 |
108 | return data
109 |
110 | def load(self, data):
111 | if 'position' in data.keys():
112 | self.position = Vector2.load(data['position'])
113 | if 'health' in data.keys():
114 | self.health = data['health']
115 | if 'deathTimer' in data.keys():
116 | self.deathTimer = data['deathTimer']
117 | if 'hostile' in data.keys():
118 | self.hostile = data['hostile']
119 | if 'name' in data.keys():
120 | self.name = data['name']
121 | if 'inventory' in data.keys():
122 | self.inventory.load(data['inventory'])
123 | if 'abilities' in data.keys():
124 | self.abilities = []
125 | for ability in data['abilities']:
126 | toAdd = Ability(self)
127 | toAdd.load(ability)
128 | self.abilities.append(toAdd)
129 | if 'ai' in data.keys():
130 | self.ai.load(data['ai'])
131 |
132 | def turn(self):
133 |
134 | for ability in self.abilities:
135 | ability.update()
136 |
137 | if self.health > 0:
138 | if self.group is not None:
139 | if self.distance(self.getLeader().position) > 20:
140 | self.teleportToLeader()
141 | self.regen()
142 | self.ai.act()
143 | else:
144 | self.deathTimer -= 1
145 |
146 | def inChunk(self, position):
147 |
148 |
149 | posX = position[0] << 4
150 | posY = position[1] << 4
151 |
152 | mobX = self.position[0]
153 | mobY = self.position[1]
154 |
155 | if mobX < posX:
156 | return False
157 |
158 | if mobX > posX + 16:
159 | return False
160 |
161 | if mobY < posY:
162 | return False
163 |
164 | if mobY > posY + 16:
165 | return False
166 |
167 | return True
168 |
169 |
170 | def canSpawn(self, position):
171 |
172 | tile = self.world.getTile(position)
173 | ground = tile.getGround()
174 | if not ground.passable:
175 | return False
176 |
177 | if not ground.spawnable:
178 | return False
179 |
180 | return True
181 |
182 | def partyCanSee(self, position):
183 | if self.group is None:
184 | return self.canSee(position)
185 |
186 | return self.group.canSee(position)
187 |
188 | def canSee(self, target):
189 |
190 | relx = abs(self.position[0] - target[0])
191 | rely = abs(self.position[1] - target[1])
192 |
193 | if relx > self.sight or rely > self.sight:
194 | return False
195 |
196 | if(relx * relx + rely * rely > self.sight * self.sight):
197 | return False
198 |
199 | if self.world.obscured(self.position, target):
200 | return False
201 |
202 | return True
203 |
204 | def inflict(self, attacker, damage):
205 |
206 | startHealth = self.health
207 |
208 | self.hostile = True
209 | self.lastAttacker = attacker
210 | self.health -= damage
211 |
212 | if self.health <= 0:
213 | self.kill()
214 |
215 | endHealth = self.health
216 |
217 | damageDealt = startHealth - endHealth
218 | self.world.log.append(attacker.getName() + " hit " + self.getName() + " for " + str(damage) + " damage!")
219 |
220 | self.lastDamage = time()
221 | self.__class__.onDamage(self.world.sounds)
222 | return damageDealt
223 |
224 | def heal(self, healer, amount):
225 | if not self.isAlive():
226 | return 0
227 |
228 | startHealth = self.health
229 | self.health += amount
230 |
231 | if self.health > self.maxHealth:
232 | self.health = self.maxHealth
233 |
234 | healingDone = self.health - startHealth
235 | self.world.log.append(healer.getName() + " healed " + self.getName() + " for " + str(healingDone) + "!")
236 | return healingDone
237 |
238 | def kill(self):
239 | self.world.log.append(self.getName() + ' Died!')
240 | self.health = 0
241 |
242 | def revive(self, reviver):
243 |
244 | if self.health != 0:
245 | return
246 |
247 | self.health = 1
248 | self.world.log.append(self.getName() + ' resurrected by ' + reviver.getName())
249 |
250 |
251 | def distance(self, location):
252 | relx = abs(self.position[0] - location[0])
253 | rely = abs(self.position[1] - location[1])
254 |
255 | return int(sqrt(relx**2 + rely**2))
256 |
257 | def acquireTarget(self):
258 |
259 | for e in self.getEnemies():
260 | if self.canSee(e.position):
261 | return e
262 |
263 | def getAttackable(self):
264 |
265 | for e in self.getEnemies():
266 | if e.isAlive() and self.canSee(e.position) and self.canHit(e.position) :
267 | return e
268 |
269 | def getEnemies(self):
270 | if self in self.world.friendly:
271 | return self.world.mobManager.mobs
272 | else:
273 | return self.world.friendly
274 |
275 | def getFriends(self):
276 | if self in self.world.friendly:
277 | return self.world.friendly
278 | else:
279 | return self.world.mobManager.mobs
280 |
281 | def canHit(self, location, attackRange):
282 | pos = Vector2(self.position[0], self.position[1])
283 | pos.center()
284 | target = Vector2(location)
285 | target.center()
286 | pos -= target
287 | return pos.inRange(attackRange)
288 |
289 | def isAlive(self):
290 | return self.health > 0
291 |
292 | def regen(self):
293 | if self.health >= self.maxHealth:
294 | return
295 |
296 | if randint(0,5) is not 0:
297 | return
298 |
299 | self.health += 1
300 |
301 | def setGroup(self, group):
302 | self.group = group
303 |
304 |
305 | def getLeader(self):
306 | return self.group.getLeader() if self.group is not None else None
307 |
308 | def teleportToLeader(self):
309 |
310 | leader = self.getLeader()
311 | x, y = leader.position
312 | r = 1
313 | while(not self.world.isLocationPassable((x, y))):
314 | x = randint(-r,r) + leader.position[0]
315 | y = randint(-r,r) + leader.position[1]
316 | if r is 10:
317 | print "Failed to find a place to teleport to"
318 | return
319 | r += 1
320 |
321 | self.position = Vector2(x, y)
322 |
323 |
324 | def getName(self):
325 | if self.name is None:
326 | return self.__class__.__name__
327 | else:
328 | return self.name
329 |
330 |
331 | def getAction(self):
332 |
333 | if len(self.abilities) == 0:
334 | return None
335 |
336 | for ability in self.abilities:
337 |
338 | for e in self.world.getAllEntities():
339 |
340 | if not ability.ready():
341 | continue
342 |
343 | if not ability.valid(e):
344 | continue
345 |
346 | return ability.cast(e.position) #ability instance
347 |
348 |
349 | def pocket(self, item):
350 | self.inventory.pocket(item)
351 |
352 | def equip(self):
353 | pass
354 |
355 | def notify(self, event):
356 | for obs in self.observers:
357 | obs.notify(self, event)
358 |
359 | def __str__(self):
360 | return self.__class__.__name__ + 'Pos: ' + str(self.position) + ' ' + 'HP: ' + str(self.health)
361 |
362 | @classmethod
363 | def onDamage(cls, sounds):
364 | sounds.get("damage.wav").play()
365 |
366 |
--------------------------------------------------------------------------------
/src/world/terrain/simplex.py:
--------------------------------------------------------------------------------
1 | # Copyright (c) 2012 Eliot Eshelman
2 | #
3 | # This program is free software: you can redistribute it and/or modify
4 | # it under the terms of the GNU General Public License as published by
5 | # the Free Software Foundation, either version 3 of the License, or
6 | # (at your option) any later version.
7 | #
8 | # This program is distributed in the hope that it will be useful,
9 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 | # GNU General Public License for more details.
12 | #
13 | # You should have received a copy of the GNU General Public License
14 | # along with this program. If not, see .
15 | ###############################################################################
16 |
17 | """2D, 3D and 4D Simplex Noise functions return 'random' values in (-1, 1).
18 | This algorithm was originally designed by Ken Perlin, but my code has been
19 | adapted from the implementation written by Stefan Gustavson (stegu@itn.liu.se)
20 | Raw Simplex noise functions return the value generated by Ken's algorithm.
21 | Scaled Raw Simplex noise functions adjust the range of values returned from the
22 | traditional (-1, 1) to whichever bounds are passed to the function.
23 | Multi-Octave Simplex noise functions compine multiple noise values to create a
24 | more complex result. Each successive layer of noise is adjusted and scaled.
25 | Scaled Multi-Octave Simplex noise functions scale the values returned from the
26 | traditional (-1,1) range to whichever range is passed to the function.
27 | In many cases, you may think you only need a 1D noise function, but in practice
28 | 2D is almost always better. For instance, if you're using the current frame
29 | number as the parameter for the noise, all objects will end up with the same
30 | noise value at each frame. By adding a second parameter on the second
31 | dimension, you can ensure that each gets a unique noise value and they don't
32 | all look identical.
33 | """
34 |
35 | import math
36 | import random
37 |
38 |
39 | class Simplex(object):
40 |
41 | def __init__(self, seed):
42 | random.seed(seed)
43 | perm = range(256)
44 | random.shuffle(perm)
45 |
46 | """Permutation table. The same list is repeated twice."""
47 | self.perm = perm + perm
48 |
49 | def octave_noise_2d(self, octaves, persistence, scale, x, y):
50 | """2D Multi-Octave Simplex noise.
51 |
52 | For each octave, a higher frequency/lower amplitude function will be added
53 | to the original. The higher the persistence [0-1], the more of each
54 | succeeding octave will be added.
55 | """
56 | total = 0.0
57 | frequency = scale
58 | amplitude = 1.0
59 |
60 | # We have to keep track of the largest possible amplitude,
61 | # because each octave adds more, and we need a value in [-1, 1].
62 | maxAmplitude = 0.0;
63 |
64 | for i in range(octaves):
65 | total += self.raw_noise_2d(x * frequency, y * frequency) * amplitude
66 | frequency *= 2.0
67 | maxAmplitude += amplitude;
68 | amplitude *= persistence
69 |
70 | return total / maxAmplitude
71 |
72 | @staticmethod
73 | def octave_noise_3d(octaves, persistence, scale, x, y, z):
74 | """3D Multi-Octave Simplex noise.
75 |
76 | For each octave, a higher frequency/lower amplitude function will be added
77 | to the original. The higher the persistence [0-1], the more of each
78 | succeeding octave will be added.
79 | """
80 | total = 0.0
81 | frequency = scale
82 | amplitude = 1.0
83 |
84 | # We have to keep track of the largest possible amplitude,
85 | # because each octave adds more, and we need a value in [-1, 1].
86 | maxAmplitude = 0.0;
87 |
88 | for i in range(octaves):
89 | total += Simplex.raw_noise_3d( x * frequency,
90 | y * frequency,
91 | z * frequency) * amplitude
92 | frequency *= 2.0
93 | maxAmplitude += amplitude;
94 | amplitude *= persistence
95 |
96 | return total / maxAmplitude
97 |
98 | @staticmethod
99 | def octave_noise_4d(octaves, persistence, scale, x, y, z, w):
100 | """4D Multi-Octave Simplex noise.
101 |
102 | For each octave, a higher frequency/lower amplitude function will be added
103 | to the original. The higher the persistence [0-1], the more of each
104 | succeeding octave will be added.
105 | """
106 | total = 0.0
107 | frequency = scale
108 | amplitude = 1.0
109 |
110 | # We have to keep track of the largest possible amplitude,
111 | # because each octave adds more, and we need a value in [-1, 1].
112 | maxAmplitude = 0.0;
113 |
114 | for i in range(octaves):
115 | total += Simplex.raw_noise_4d( x * frequency,
116 | y * frequency,
117 | z * frequency,
118 | w * frequency) * amplitude
119 | frequency *= 2.0
120 | maxAmplitude += amplitude;
121 | amplitude *= persistence
122 |
123 | return total / maxAmplitude
124 |
125 | @staticmethod
126 | def scaled_octave_noise_2d(octaves, persistence, scale, loBound, hiBound, x, y):
127 | """2D Scaled Multi-Octave Simplex noise.
128 |
129 | Returned value will be between loBound and hiBound.
130 | """
131 | return (Simplex.octave_noise_2d(octaves, persistence, scale, x, y) *
132 | (hiBound - loBound) / 2 +
133 | (hiBound + loBound) / 2)
134 |
135 | @staticmethod
136 | def scaled_octave_noise_3d(octaves, persistence, scale, loBound, hiBound, x, y, z):
137 | """3D Scaled Multi-Octave Simplex noise.
138 |
139 | Returned value will be between loBound and hiBound.
140 | """
141 | return (Simplex.octave_noise_3d(octaves, persistence, scale, x, y, z) *
142 | (hiBound - loBound) / 2 +
143 | (hiBound + loBound) / 2)
144 |
145 | @staticmethod
146 | def scaled_octave_noise_4d(octaves, persistence, scale, loBound, hiBound, x, y, z, w):
147 | """4D Scaled Multi-Octave Simplex noise.
148 |
149 | Returned value will be between loBound and hiBound.
150 | """
151 | return (Simplex.octave_noise_4d(octaves, persistence, scale, x, y, z, w) *
152 | (hiBound - loBound) / 2 +
153 | (hiBound + loBound) / 2)
154 |
155 | @staticmethod
156 | def scaled_raw_noise_2d(loBound, hiBound, x, y):
157 | """2D Scaled Raw Simplex noise.
158 |
159 | Returned value will be between loBound and hiBound.
160 | """
161 | return (Simplex.raw_noise_2d(x, y) *
162 | (hiBound - loBound) / 2+
163 | (hiBound + loBound) / 2)
164 |
165 | @staticmethod
166 | def scaled_raw_noise_3d(loBound, hiBound, x, y, z):
167 | """3D Scaled Raw Simplex noise.
168 |
169 | Returned value will be between loBound and hiBound.
170 | """
171 | return (Simplex.raw_noise_3d(x, y, z) *
172 | (hiBound - loBound) / 2+
173 | (hiBound + loBound) / 2)
174 |
175 | @staticmethod
176 | def scaled_raw_noise_4d(loBound, hiBound, x, y, z, w):
177 | """4D Scaled Raw Simplex noise.
178 |
179 | Returned value will be between loBound and hiBound.
180 | """
181 | return (Simplex.raw_noise_4d(x, y, z, w) *
182 | (hiBound - loBound) / 2+
183 | (hiBound + loBound) / 2)
184 |
185 | def raw_noise_2d(self, x, y):
186 | """2D Raw Simplex noise."""
187 | # Noise contributions from the three corners
188 | n0, n1, n2 = 0.0, 0.0, 0.0
189 |
190 | # Skew the input space to determine which simplex cell we're in
191 | F2 = 0.5 * (math.sqrt(3.0) - 1.0)
192 | # Hairy skew factor for 2D
193 | s = (x + y) * F2
194 | i = Simplex.fastfloor(x + s)
195 | j = Simplex.fastfloor(y + s)
196 |
197 | G2 = (3.0 - math.sqrt(3.0)) / 6.0
198 | t = float(i + j) * G2
199 | # Unskew the cell origin back to (x,y) space
200 | X0 = i - t
201 | Y0 = j - t
202 | # The x,y distances from the cell origin
203 | x0 = x - X0
204 | y0 = y - Y0
205 |
206 | # For the 2D case, the simplex shape is an equilateral triangle.
207 | # Determine which simplex we are in.
208 | i1, j1 = 0, 0 # Offsets for second (middle) corner of simplex in (i,j) coords
209 | if x0 > y0: # lower triangle, XY order: (0,0)->(1,0)->(1,1)
210 | i1 = 1
211 | j1 = 0
212 | else: # upper triangle, YX order: (0,0)->(0,1)->(1,1)
213 | i1 = 0
214 | j1 = 1
215 |
216 | # A step of (1,0) in (i,j) means a step of (1-c,-c) in (x,y), and
217 | # a step of (0,1) in (i,j) means a step of (-c,1-c) in (x,y), where
218 | # c = (3-sqrt(3))/6
219 | x1 = x0 - i1 + G2 # Offsets for middle corner in (x,y) unskewed coords
220 | y1 = y0 - j1 + G2
221 | x2 = x0 - 1.0 + 2.0 * G2 # Offsets for last corner in (x,y) unskewed coords
222 | y2 = y0 - 1.0 + 2.0 * G2
223 |
224 | # Work out the hashed gradient indices of the three simplex corners
225 | ii = int(i) & 255
226 | jj = int(j) & 255
227 | gi0 = self.perm[ii+self.perm[jj]] % 12
228 | gi1 = self.perm[ii+i1+self.perm[jj+j1]] % 12
229 | gi2 = self.perm[ii+1+self.perm[jj+1]] % 12
230 |
231 | # Calculate the contribution from the three corners
232 | t0 = 0.5 - x0*x0 - y0*y0
233 | if t0 < 0:
234 | n0 = 0.0
235 | else:
236 | t0 *= t0
237 | n0 = t0 * t0 * Simplex.dot2d(Simplex._grad3[gi0], x0, y0)
238 |
239 | t1 = 0.5 - x1*x1 - y1*y1
240 | if t1 < 0:
241 | n1 = 0.0
242 | else:
243 | t1 *= t1
244 | n1 = t1 * t1 * Simplex.dot2d(Simplex._grad3[gi1], x1, y1)
245 |
246 | t2 = 0.5 - x2*x2-y2*y2
247 | if t2 < 0:
248 | n2 = 0.0
249 | else:
250 | t2 *= t2
251 | n2 = t2 * t2 * Simplex.dot2d(Simplex._grad3[gi2], x2, y2)
252 |
253 | # Add contributions from each corner to get the final noise value.
254 | # The result is scaled to return values in the interval [-1,1].
255 | return 70.0 * (n0 + n1 + n2)
256 |
257 | def raw_noise_3d(self, x, y, z):
258 | """3D Raw Simplex noise."""
259 | # Noise contributions from the four corners
260 | n0, n1, n2, n3 = 0.0, 0.0, 0.0, 0.0
261 |
262 | # Skew the input space to determine which simplex cell we're in
263 | F3 = 1.0/3.0
264 | # Very nice and simple skew factor for 3D
265 | s = (x+y+z) * F3
266 | i = int(x + s)
267 | j = int(y + s)
268 | k = int(z + s)
269 |
270 | G3 = 1.0 / 6.0
271 | t = float(i+j+k) * G3
272 | # Unskew the cell origin back to (x,y,z) space
273 | X0 = i - t
274 | Y0 = j - t
275 | Z0 = k - t
276 | # The x,y,z distances from the cell origin
277 | x0 = x - X0
278 | y0 = y - Y0
279 | z0 = z - Z0
280 |
281 | # For the 3D case, the simplex shape is a slightly irregular tetrahedron.
282 | # Determine which simplex we are in.
283 | i1, j1, k1 = 0,0,0 # Offsets for second corner of simplex in (i,j,k) coords
284 | i2, j2, k2 = 0,0,0 # Offsets for third corner of simplex in (i,j,k) coords
285 |
286 | if x0 >= y0:
287 | if y0 >= z0: # X Y Z order
288 | i1 = 1
289 | j1 = 0
290 | k1 = 0
291 | i2 = 1
292 | j2 = 1
293 | k2 = 0
294 | elif x0 >= z0: # X Z Y order
295 | i1 = 1
296 | j1 = 0
297 | k1 = 0
298 | i2 = 1
299 | j2 = 0
300 | k2 = 1
301 | else: # Z X Y order
302 | i1 = 0
303 | j1 = 0
304 | k1 = 1
305 | i2 = 1
306 | j2 = 0
307 | k2 = 1
308 | else:
309 | if y0 < z0: # Z Y X order
310 | i1 = 0
311 | j1 = 0
312 | k1 = 1
313 | i2 = 0
314 | j2 = 1
315 | k2 = 1
316 | elif x0 < z0: # Y Z X order
317 | i1 = 0
318 | j1 = 1
319 | k1 = 0
320 | i2 = 0
321 | j2 = 1
322 | k2 = 1
323 | else: # Y X Z order
324 | i1 = 0
325 | j1 = 1
326 | k1 = 0
327 | i2 = 1
328 | j2 = 1
329 | k2 = 0
330 |
331 | # A step of (1,0,0) in (i,j,k) means a step of (1-c,-c,-c) in (x,y,z),
332 | # a step of (0,1,0) in (i,j,k) means a step of (-c,1-c,-c) in (x,y,z), and
333 | # a step of (0,0,1) in (i,j,k) means a step of (-c,-c,1-c) in (x,y,z), where
334 | # c = 1/6.
335 | x1 = x0 - i1 + G3 # Offsets for second corner in (x,y,z) coords
336 | y1 = y0 - j1 + G3
337 | z1 = z0 - k1 + G3
338 | x2 = x0 - i2 + 2.0*G3 # Offsets for third corner in (x,y,z) coords
339 | y2 = y0 - j2 + 2.0*G3
340 | z2 = z0 - k2 + 2.0*G3
341 | x3 = x0 - 1.0 + 3.0*G3 # Offsets for last corner in (x,y,z) coords
342 | y3 = y0 - 1.0 + 3.0*G3
343 | z3 = z0 - 1.0 + 3.0*G3
344 |
345 | # Work out the hashed gradient indices of the four simplex corners
346 | ii = int(i) & 255
347 | jj = int(j) & 255
348 | kk = int(k) & 255
349 | gi0 = self.perm[ii+self.perm[jj+self.perm[kk]]] % 12
350 | gi1 = self.perm[ii+i1+self.perm[jj+j1+self.perm[kk+k1]]] % 12
351 | gi2 = self.perm[ii+i2+self.perm[jj+j2+self.perm[kk+k2]]] % 12
352 | gi3 = self.perm[ii+1+self.perm[jj+1+self.perm[kk+1]]] % 12
353 |
354 | # Calculate the contribution from the four corners
355 | t0 = 0.6 - x0*x0 - y0*y0 - z0*z0
356 | if t0 < 0:
357 | n0 = 0.0
358 | else:
359 | t0 *= t0
360 | n0 = t0 * t0 * Simplex.dot3d(Simplex._grad3[gi0], x0, y0, z0)
361 |
362 | t1 = 0.6 - x1*x1 - y1*y1 - z1*z1
363 | if t1 < 0:
364 | n1 = 0.0
365 | else:
366 | t1 *= t1
367 | n1 = t1 * t1 * Simplex.dot3d(Simplex._grad3[gi1], x1, y1, z1)
368 |
369 | t2 = 0.6 - x2*x2 - y2*y2 - z2*z2
370 | if t2 < 0:
371 | n2 = 0.0
372 | else:
373 | t2 *= t2
374 | n2 = t2 * t2 * Simplex.dot3d(Simplex._grad3[gi2], x2, y2, z2)
375 |
376 | t3 = 0.6 - x3*x3 - y3*y3 - z3*z3
377 | if t3 < 0:
378 | n3 = 0.0
379 | else:
380 | t3 *= t3
381 | n3 = t3 * t3 * Simplex.dot3d(Simplex._grad3[gi3], x3, y3, z3)
382 |
383 | # Add contributions from each corner to get the final noise value.
384 | # The result is scaled to stay just inside [-1,1]
385 | return 32.0 * (n0 + n1 + n2 + n3)
386 |
387 | def raw_noise_4d(self, x, y, z, w):
388 | """4D Raw Simplex noise."""
389 | # Noise contributions from the five corners
390 | n0, n1, n2, n3, n4 = 0.0, 0.0, 0.0, 0.0, 0.0
391 |
392 | # The skewing and unskewing factors are hairy again for the 4D case
393 | F4 = (math.sqrt(5.0)-1.0) / 4.0
394 | # Skew the (x,y,z,w) space to determine which cell of 24 simplices we're in
395 | s = (x + y + z + w) * F4
396 | i = int(x + s)
397 | j = int(y + s)
398 | k = int(z + s)
399 | l = int(w + s)
400 |
401 | G4 = (5.0-math.sqrt(5.0)) / 20.0
402 | t = (i + j + k + l) * G4
403 | # Unskew the cell origin back to (x,y,z,w) space
404 | X0 = i - t
405 | Y0 = j - t
406 | Z0 = k - t
407 | W0 = l - t
408 | # The x,y,z,w distances from the cell origin
409 | x0 = x - X0
410 | y0 = y - Y0
411 | z0 = z - Z0
412 | w0 = w - W0
413 |
414 | # For the 4D case, the simplex is a 4D shape I won't even try to describe.
415 | # To find out which of the 24 possible simplices we're in, we need to
416 | # determine the magnitude ordering of x0, y0, z0 and w0.
417 | # The method below is a good way of finding the ordering of x,y,z,w and
418 | # then find the correct traversal order for the simplex we're in.
419 | # First, six pair-wise comparisons are performed between each possible pair
420 | # of the four coordinates, and the results are used to add up binary bits
421 | # for an integer index.
422 | c1 = 32 if x0 > y0 else 0
423 | c2 = 16 if x0 > z0 else 0
424 | c3 = 8 if y0 > z0 else 0
425 | c4 = 4 if x0 > w0 else 0
426 | c5 = 2 if y0 > w0 else 0
427 | c6 = 1 if z0 > w0 else 0
428 | c = c1 + c2 + c3 + c4 + c5 + c6
429 |
430 | i1, j1, k1, l1 = 0,0,0,0 # The integer offsets for the second simplex corner
431 | i2, j2, k2, l2 = 0,0,0,0 # The integer offsets for the third simplex corner
432 | i3, j3, k3, l3 = 0,0,0,0 # The integer offsets for the fourth simplex corner
433 |
434 | # simplex[c] is a 4-vector with the numbers 0, 1, 2 and 3 in some order.
435 | # Many values of c will never occur, since e.g. x>y>z>w makes x= 3 else 0
440 | j1 = 1 if Simplex._simplex[c][1] >= 3 else 0
441 | k1 = 1 if Simplex._simplex[c][2] >= 3 else 0
442 | l1 = 1 if Simplex._simplex[c][3] >= 3 else 0
443 | # The number 2 in the "simplex" array is at the second largest coordinate.
444 | i2 = 1 if Simplex._simplex[c][0] >= 2 else 0
445 | j2 = 1 if Simplex._simplex[c][1] >= 2 else 0
446 | k2 = 1 if Simplex._simplex[c][2] >= 2 else 0
447 | l2 = 1 if Simplex._simplex[c][3] >= 2 else 0
448 | # The number 1 in the "simplex" array is at the second smallest coordinate.
449 | i3 = 1 if Simplex._simplex[c][0] >= 1 else 0
450 | j3 = 1 if Simplex._simplex[c][1] >= 1 else 0
451 | k3 = 1 if Simplex._simplex[c][2] >= 1 else 0
452 | l3 = 1 if Simplex._simplex[c][3] >= 1 else 0
453 | # The fifth corner has all coordinate offsets = 1, so no need to look that up.
454 | x1 = x0 - i1 + G4 # Offsets for second corner in (x,y,z,w) coords
455 | y1 = y0 - j1 + G4
456 | z1 = z0 - k1 + G4
457 | w1 = w0 - l1 + G4
458 | x2 = x0 - i2 + 2.0*G4 # Offsets for third corner in (x,y,z,w) coords
459 | y2 = y0 - j2 + 2.0*G4
460 | z2 = z0 - k2 + 2.0*G4
461 | w2 = w0 - l2 + 2.0*G4
462 | x3 = x0 - i3 + 3.0*G4 # Offsets for fourth corner in (x,y,z,w) coords
463 | y3 = y0 - j3 + 3.0*G4
464 | z3 = z0 - k3 + 3.0*G4
465 | w3 = w0 - l3 + 3.0*G4
466 | x4 = x0 - 1.0 + 4.0*G4 # Offsets for last corner in (x,y,z,w) coords
467 | y4 = y0 - 1.0 + 4.0*G4
468 | z4 = z0 - 1.0 + 4.0*G4
469 | w4 = w0 - 1.0 + 4.0*G4
470 |
471 | # Work out the hashed gradient indices of the five simplex corners
472 | ii = int(i) & 255
473 | jj = int(j) & 255
474 | kk = int(k) & 255
475 | ll = int(l) & 255
476 | gi0 = self.perm[ii+self.perm[jj+self.perm[kk+self.perm[ll]]]] % 32
477 | gi1 = self.perm[ii+i1+self.perm[jj+j1+self.perm[kk+k1+self.perm[ll+l1]]]] % 32
478 | gi2 = self.perm[ii+i2+self.perm[jj+j2+self.perm[kk+k2+self.perm[ll+l2]]]] % 32
479 | gi3 = self.perm[ii+i3+self.perm[jj+j3+self.perm[kk+k3+self.perm[ll+l3]]]] % 32
480 | gi4 = self.perm[ii+1+self.perm[jj+1+self.perm[kk+1+self.perm[ll+1]]]] % 32
481 |
482 | # Calculate the contribution from the five corners
483 | t0 = 0.6 - x0*x0 - y0*y0 - z0*z0 - w0*w0
484 | if t0 < 0:
485 | n0 = 0.0
486 | else:
487 | t0 *= t0
488 | n0 = t0 * t0 * Simplex.dot4d(Simplex._grad4[gi0], x0, y0, z0, w0)
489 |
490 | t1 = 0.6 - x1*x1 - y1*y1 - z1*z1 - w1*w1
491 | if t1 < 0:
492 | n1 = 0.0
493 | else:
494 | t1 *= t1
495 | n1 = t1 * t1 * Simplex.dot4d(Simplex._grad4[gi1], x1, y1, z1, w1)
496 |
497 | t2 = 0.6 - x2*x2 - y2*y2 - z2*z2 - w2*w2
498 | if t2 < 0:
499 | n2 = 0.0
500 | else:
501 | t2 *= t2
502 | n2 = t2 * t2 * Simplex.dot4d(Simplex._grad4[gi2], x2, y2, z2, w2)
503 |
504 | t3 = 0.6 - x3*x3 - y3*y3 - z3*z3 - w3*w3
505 | if t3 < 0:
506 | n3 = 0.0
507 | else:
508 | t3 *= t3
509 | n3 = t3 * t3 * Simplex.dot4d(Simplex._grad4[gi3], x3, y3, z3, w3)
510 |
511 | t4 = 0.6 - x4*x4 - y4*y4 - z4*z4 - w4*w4
512 | if t4 < 0:
513 | n4 = 0.0
514 | else:
515 | t4 *= t4
516 | n4 = t4 * t4 * Simplex.dot4d(Simplex._grad4[gi4], x4, y4, z4, w4)
517 |
518 | # Sum up and scale the result to cover the range [-1,1]
519 | return 27.0 * (n0 + n1 + n2 + n3 + n4)
520 |
521 | @staticmethod
522 | def dot2d(g, x, y):
523 | return g[0]*x + g[1]*y
524 |
525 | @staticmethod
526 | def dot3d(g, x, y, z):
527 | return g[0]*x + g[1]*y + g[2]*z
528 |
529 | @staticmethod
530 | def dot4d(g, x, y, z, w):
531 | return g[0]*x + g[1]*y + g[2]*z + g[3]*w
532 |
533 |
534 | """The gradients are the midpoints of the vertices of a cube."""
535 | _grad3 = [
536 | [1,1,0], [-1,1,0], [1,-1,0], [-1,-1,0],
537 | [1,0,1], [-1,0,1], [1,0,-1], [-1,0,-1],
538 | [0,1,1], [0,-1,1], [0,1,-1], [0,-1,-1]
539 | ]
540 |
541 | """The gradients are the midpoints of the vertices of a cube."""
542 | _grad4 = [
543 | [0,1,1,1], [0,1,1,-1], [0,1,-1,1], [0,1,-1,-1],
544 | [0,-1,1,1], [0,-1,1,-1], [0,-1,-1,1], [0,-1,-1,-1],
545 | [1,0,1,1], [1,0,1,-1], [1,0,-1,1], [1,0,-1,-1],
546 | [-1,0,1,1], [-1,0,1,-1], [-1,0,-1,1], [-1,0,-1,-1],
547 | [1,1,0,1], [1,1,0,-1], [1,-1,0,1], [1,-1,0,-1],
548 | [-1,1,0,1], [-1,1,0,-1], [-1,-1,0,1], [-1,-1,0,-1],
549 | [1,1,1,0], [1,1,-1,0], [1,-1,1,0], [1,-1,-1,0],
550 | [-1,1,1,0], [-1,1,-1,0], [-1,-1,1,0], [-1,-1,-1,0]
551 | ]
552 |
553 | """A lookup table to traverse the simplex around a given point in 4D."""
554 | _simplex = [
555 | [0,1,2,3],[0,1,3,2],[0,0,0,0],[0,2,3,1],[0,0,0,0],[0,0,0,0],[0,0,0,0],[1,2,3,0],
556 | [0,2,1,3],[0,0,0,0],[0,3,1,2],[0,3,2,1],[0,0,0,0],[0,0,0,0],[0,0,0,0],[1,3,2,0],
557 | [0,0,0,0],[0,0,0,0],[0,0,0,0],[0,0,0,0],[0,0,0,0],[0,0,0,0],[0,0,0,0],[0,0,0,0],
558 | [1,2,0,3],[0,0,0,0],[1,3,0,2],[0,0,0,0],[0,0,0,0],[0,0,0,0],[2,3,0,1],[2,3,1,0],
559 | [1,0,2,3],[1,0,3,2],[0,0,0,0],[0,0,0,0],[0,0,0,0],[2,0,3,1],[0,0,0,0],[2,1,3,0],
560 | [0,0,0,0],[0,0,0,0],[0,0,0,0],[0,0,0,0],[0,0,0,0],[0,0,0,0],[0,0,0,0],[0,0,0,0],
561 | [2,0,1,3],[0,0,0,0],[0,0,0,0],[0,0,0,0],[3,0,1,2],[3,0,2,1],[0,0,0,0],[3,1,2,0],
562 | [2,1,0,3],[0,0,0,0],[0,0,0,0],[0,0,0,0],[3,1,0,2],[0,0,0,0],[3,2,0,1],[3,2,1,0]
563 | ]
564 |
565 | @staticmethod
566 | def fastfloor(n):
567 |
568 | if(n < 0):
569 | return int(n - 1)
570 |
571 | return int(n)
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | GNU GENERAL PUBLIC LICENSE
2 | Version 3, 29 June 2007
3 |
4 | Copyright (C) 2007 Free Software Foundation, Inc.
5 | Everyone is permitted to copy and distribute verbatim copies
6 | of this license document, but changing it is not allowed.
7 |
8 | Preamble
9 |
10 | The GNU General Public License is a free, copyleft license for
11 | software and other kinds of works.
12 |
13 | The licenses for most software and other practical works are designed
14 | to take away your freedom to share and change the works. By contrast,
15 | the GNU General Public License is intended to guarantee your freedom to
16 | share and change all versions of a program--to make sure it remains free
17 | software for all its users. We, the Free Software Foundation, use the
18 | GNU General Public License for most of our software; it applies also to
19 | any other work released this way by its authors. You can apply it to
20 | your programs, too.
21 |
22 | When we speak of free software, we are referring to freedom, not
23 | price. Our General Public Licenses are designed to make sure that you
24 | have the freedom to distribute copies of free software (and charge for
25 | them if you wish), that you receive source code or can get it if you
26 | want it, that you can change the software or use pieces of it in new
27 | free programs, and that you know you can do these things.
28 |
29 | To protect your rights, we need to prevent others from denying you
30 | these rights or asking you to surrender the rights. Therefore, you have
31 | certain responsibilities if you distribute copies of the software, or if
32 | you modify it: responsibilities to respect the freedom of others.
33 |
34 | For example, if you distribute copies of such a program, whether
35 | gratis or for a fee, you must pass on to the recipients the same
36 | freedoms that you received. You must make sure that they, too, receive
37 | or can get the source code. And you must show them these terms so they
38 | know their rights.
39 |
40 | Developers that use the GNU GPL protect your rights with two steps:
41 | (1) assert copyright on the software, and (2) offer you this License
42 | giving you legal permission to copy, distribute and/or modify it.
43 |
44 | For the developers' and authors' protection, the GPL clearly explains
45 | that there is no warranty for this free software. For both users' and
46 | authors' sake, the GPL requires that modified versions be marked as
47 | changed, so that their problems will not be attributed erroneously to
48 | authors of previous versions.
49 |
50 | Some devices are designed to deny users access to install or run
51 | modified versions of the software inside them, although the manufacturer
52 | can do so. This is fundamentally incompatible with the aim of
53 | protecting users' freedom to change the software. The systematic
54 | pattern of such abuse occurs in the area of products for individuals to
55 | use, which is precisely where it is most unacceptable. Therefore, we
56 | have designed this version of the GPL to prohibit the practice for those
57 | products. If such problems arise substantially in other domains, we
58 | stand ready to extend this provision to those domains in future versions
59 | of the GPL, as needed to protect the freedom of users.
60 |
61 | Finally, every program is threatened constantly by software patents.
62 | States should not allow patents to restrict development and use of
63 | software on general-purpose computers, but in those that do, we wish to
64 | avoid the special danger that patents applied to a free program could
65 | make it effectively proprietary. To prevent this, the GPL assures that
66 | patents cannot be used to render the program non-free.
67 |
68 | The precise terms and conditions for copying, distribution and
69 | modification follow.
70 |
71 | TERMS AND CONDITIONS
72 |
73 | 0. Definitions.
74 |
75 | "This License" refers to version 3 of the GNU General Public License.
76 |
77 | "Copyright" also means copyright-like laws that apply to other kinds of
78 | works, such as semiconductor masks.
79 |
80 | "The Program" refers to any copyrightable work licensed under this
81 | License. Each licensee is addressed as "you". "Licensees" and
82 | "recipients" may be individuals or organizations.
83 |
84 | To "modify" a work means to copy from or adapt all or part of the work
85 | in a fashion requiring copyright permission, other than the making of an
86 | exact copy. The resulting work is called a "modified version" of the
87 | earlier work or a work "based on" the earlier work.
88 |
89 | A "covered work" means either the unmodified Program or a work based
90 | on the Program.
91 |
92 | To "propagate" a work means to do anything with it that, without
93 | permission, would make you directly or secondarily liable for
94 | infringement under applicable copyright law, except executing it on a
95 | computer or modifying a private copy. Propagation includes copying,
96 | distribution (with or without modification), making available to the
97 | public, and in some countries other activities as well.
98 |
99 | To "convey" a work means any kind of propagation that enables other
100 | parties to make or receive copies. Mere interaction with a user through
101 | a computer network, with no transfer of a copy, is not conveying.
102 |
103 | An interactive user interface displays "Appropriate Legal Notices"
104 | to the extent that it includes a convenient and prominently visible
105 | feature that (1) displays an appropriate copyright notice, and (2)
106 | tells the user that there is no warranty for the work (except to the
107 | extent that warranties are provided), that licensees may convey the
108 | work under this License, and how to view a copy of this License. If
109 | the interface presents a list of user commands or options, such as a
110 | menu, a prominent item in the list meets this criterion.
111 |
112 | 1. Source Code.
113 |
114 | The "source code" for a work means the preferred form of the work
115 | for making modifications to it. "Object code" means any non-source
116 | form of a work.
117 |
118 | A "Standard Interface" means an interface that either is an official
119 | standard defined by a recognized standards body, or, in the case of
120 | interfaces specified for a particular programming language, one that
121 | is widely used among developers working in that language.
122 |
123 | The "System Libraries" of an executable work include anything, other
124 | than the work as a whole, that (a) is included in the normal form of
125 | packaging a Major Component, but which is not part of that Major
126 | Component, and (b) serves only to enable use of the work with that
127 | Major Component, or to implement a Standard Interface for which an
128 | implementation is available to the public in source code form. A
129 | "Major Component", in this context, means a major essential component
130 | (kernel, window system, and so on) of the specific operating system
131 | (if any) on which the executable work runs, or a compiler used to
132 | produce the work, or an object code interpreter used to run it.
133 |
134 | The "Corresponding Source" for a work in object code form means all
135 | the source code needed to generate, install, and (for an executable
136 | work) run the object code and to modify the work, including scripts to
137 | control those activities. However, it does not include the work's
138 | System Libraries, or general-purpose tools or generally available free
139 | programs which are used unmodified in performing those activities but
140 | which are not part of the work. For example, Corresponding Source
141 | includes interface definition files associated with source files for
142 | the work, and the source code for shared libraries and dynamically
143 | linked subprograms that the work is specifically designed to require,
144 | such as by intimate data communication or control flow between those
145 | subprograms and other parts of the work.
146 |
147 | The Corresponding Source need not include anything that users
148 | can regenerate automatically from other parts of the Corresponding
149 | Source.
150 |
151 | The Corresponding Source for a work in source code form is that
152 | same work.
153 |
154 | 2. Basic Permissions.
155 |
156 | All rights granted under this License are granted for the term of
157 | copyright on the Program, and are irrevocable provided the stated
158 | conditions are met. This License explicitly affirms your unlimited
159 | permission to run the unmodified Program. The output from running a
160 | covered work is covered by this License only if the output, given its
161 | content, constitutes a covered work. This License acknowledges your
162 | rights of fair use or other equivalent, as provided by copyright law.
163 |
164 | You may make, run and propagate covered works that you do not
165 | convey, without conditions so long as your license otherwise remains
166 | in force. You may convey covered works to others for the sole purpose
167 | of having them make modifications exclusively for you, or provide you
168 | with facilities for running those works, provided that you comply with
169 | the terms of this License in conveying all material for which you do
170 | not control copyright. Those thus making or running the covered works
171 | for you must do so exclusively on your behalf, under your direction
172 | and control, on terms that prohibit them from making any copies of
173 | your copyrighted material outside their relationship with you.
174 |
175 | Conveying under any other circumstances is permitted solely under
176 | the conditions stated below. Sublicensing is not allowed; section 10
177 | makes it unnecessary.
178 |
179 | 3. Protecting Users' Legal Rights From Anti-Circumvention Law.
180 |
181 | No covered work shall be deemed part of an effective technological
182 | measure under any applicable law fulfilling obligations under article
183 | 11 of the WIPO copyright treaty adopted on 20 December 1996, or
184 | similar laws prohibiting or restricting circumvention of such
185 | measures.
186 |
187 | When you convey a covered work, you waive any legal power to forbid
188 | circumvention of technological measures to the extent such circumvention
189 | is effected by exercising rights under this License with respect to
190 | the covered work, and you disclaim any intention to limit operation or
191 | modification of the work as a means of enforcing, against the work's
192 | users, your or third parties' legal rights to forbid circumvention of
193 | technological measures.
194 |
195 | 4. Conveying Verbatim Copies.
196 |
197 | You may convey verbatim copies of the Program's source code as you
198 | receive it, in any medium, provided that you conspicuously and
199 | appropriately publish on each copy an appropriate copyright notice;
200 | keep intact all notices stating that this License and any
201 | non-permissive terms added in accord with section 7 apply to the code;
202 | keep intact all notices of the absence of any warranty; and give all
203 | recipients a copy of this License along with the Program.
204 |
205 | You may charge any price or no price for each copy that you convey,
206 | and you may offer support or warranty protection for a fee.
207 |
208 | 5. Conveying Modified Source Versions.
209 |
210 | You may convey a work based on the Program, or the modifications to
211 | produce it from the Program, in the form of source code under the
212 | terms of section 4, provided that you also meet all of these conditions:
213 |
214 | a) The work must carry prominent notices stating that you modified
215 | it, and giving a relevant date.
216 |
217 | b) The work must carry prominent notices stating that it is
218 | released under this License and any conditions added under section
219 | 7. This requirement modifies the requirement in section 4 to
220 | "keep intact all notices".
221 |
222 | c) You must license the entire work, as a whole, under this
223 | License to anyone who comes into possession of a copy. This
224 | License will therefore apply, along with any applicable section 7
225 | additional terms, to the whole of the work, and all its parts,
226 | regardless of how they are packaged. This License gives no
227 | permission to license the work in any other way, but it does not
228 | invalidate such permission if you have separately received it.
229 |
230 | d) If the work has interactive user interfaces, each must display
231 | Appropriate Legal Notices; however, if the Program has interactive
232 | interfaces that do not display Appropriate Legal Notices, your
233 | work need not make them do so.
234 |
235 | A compilation of a covered work with other separate and independent
236 | works, which are not by their nature extensions of the covered work,
237 | and which are not combined with it such as to form a larger program,
238 | in or on a volume of a storage or distribution medium, is called an
239 | "aggregate" if the compilation and its resulting copyright are not
240 | used to limit the access or legal rights of the compilation's users
241 | beyond what the individual works permit. Inclusion of a covered work
242 | in an aggregate does not cause this License to apply to the other
243 | parts of the aggregate.
244 |
245 | 6. Conveying Non-Source Forms.
246 |
247 | You may convey a covered work in object code form under the terms
248 | of sections 4 and 5, provided that you also convey the
249 | machine-readable Corresponding Source under the terms of this License,
250 | in one of these ways:
251 |
252 | a) Convey the object code in, or embodied in, a physical product
253 | (including a physical distribution medium), accompanied by the
254 | Corresponding Source fixed on a durable physical medium
255 | customarily used for software interchange.
256 |
257 | b) Convey the object code in, or embodied in, a physical product
258 | (including a physical distribution medium), accompanied by a
259 | written offer, valid for at least three years and valid for as
260 | long as you offer spare parts or customer support for that product
261 | model, to give anyone who possesses the object code either (1) a
262 | copy of the Corresponding Source for all the software in the
263 | product that is covered by this License, on a durable physical
264 | medium customarily used for software interchange, for a price no
265 | more than your reasonable cost of physically performing this
266 | conveying of source, or (2) access to copy the
267 | Corresponding Source from a network server at no charge.
268 |
269 | c) Convey individual copies of the object code with a copy of the
270 | written offer to provide the Corresponding Source. This
271 | alternative is allowed only occasionally and noncommercially, and
272 | only if you received the object code with such an offer, in accord
273 | with subsection 6b.
274 |
275 | d) Convey the object code by offering access from a designated
276 | place (gratis or for a charge), and offer equivalent access to the
277 | Corresponding Source in the same way through the same place at no
278 | further charge. You need not require recipients to copy the
279 | Corresponding Source along with the object code. If the place to
280 | copy the object code is a network server, the Corresponding Source
281 | may be on a different server (operated by you or a third party)
282 | that supports equivalent copying facilities, provided you maintain
283 | clear directions next to the object code saying where to find the
284 | Corresponding Source. Regardless of what server hosts the
285 | Corresponding Source, you remain obligated to ensure that it is
286 | available for as long as needed to satisfy these requirements.
287 |
288 | e) Convey the object code using peer-to-peer transmission, provided
289 | you inform other peers where the object code and Corresponding
290 | Source of the work are being offered to the general public at no
291 | charge under subsection 6d.
292 |
293 | A separable portion of the object code, whose source code is excluded
294 | from the Corresponding Source as a System Library, need not be
295 | included in conveying the object code work.
296 |
297 | A "User Product" is either (1) a "consumer product", which means any
298 | tangible personal property which is normally used for personal, family,
299 | or household purposes, or (2) anything designed or sold for incorporation
300 | into a dwelling. In determining whether a product is a consumer product,
301 | doubtful cases shall be resolved in favor of coverage. For a particular
302 | product received by a particular user, "normally used" refers to a
303 | typical or common use of that class of product, regardless of the status
304 | of the particular user or of the way in which the particular user
305 | actually uses, or expects or is expected to use, the product. A product
306 | is a consumer product regardless of whether the product has substantial
307 | commercial, industrial or non-consumer uses, unless such uses represent
308 | the only significant mode of use of the product.
309 |
310 | "Installation Information" for a User Product means any methods,
311 | procedures, authorization keys, or other information required to install
312 | and execute modified versions of a covered work in that User Product from
313 | a modified version of its Corresponding Source. The information must
314 | suffice to ensure that the continued functioning of the modified object
315 | code is in no case prevented or interfered with solely because
316 | modification has been made.
317 |
318 | If you convey an object code work under this section in, or with, or
319 | specifically for use in, a User Product, and the conveying occurs as
320 | part of a transaction in which the right of possession and use of the
321 | User Product is transferred to the recipient in perpetuity or for a
322 | fixed term (regardless of how the transaction is characterized), the
323 | Corresponding Source conveyed under this section must be accompanied
324 | by the Installation Information. But this requirement does not apply
325 | if neither you nor any third party retains the ability to install
326 | modified object code on the User Product (for example, the work has
327 | been installed in ROM).
328 |
329 | The requirement to provide Installation Information does not include a
330 | requirement to continue to provide support service, warranty, or updates
331 | for a work that has been modified or installed by the recipient, or for
332 | the User Product in which it has been modified or installed. Access to a
333 | network may be denied when the modification itself materially and
334 | adversely affects the operation of the network or violates the rules and
335 | protocols for communication across the network.
336 |
337 | Corresponding Source conveyed, and Installation Information provided,
338 | in accord with this section must be in a format that is publicly
339 | documented (and with an implementation available to the public in
340 | source code form), and must require no special password or key for
341 | unpacking, reading or copying.
342 |
343 | 7. Additional Terms.
344 |
345 | "Additional permissions" are terms that supplement the terms of this
346 | License by making exceptions from one or more of its conditions.
347 | Additional permissions that are applicable to the entire Program shall
348 | be treated as though they were included in this License, to the extent
349 | that they are valid under applicable law. If additional permissions
350 | apply only to part of the Program, that part may be used separately
351 | under those permissions, but the entire Program remains governed by
352 | this License without regard to the additional permissions.
353 |
354 | When you convey a copy of a covered work, you may at your option
355 | remove any additional permissions from that copy, or from any part of
356 | it. (Additional permissions may be written to require their own
357 | removal in certain cases when you modify the work.) You may place
358 | additional permissions on material, added by you to a covered work,
359 | for which you have or can give appropriate copyright permission.
360 |
361 | Notwithstanding any other provision of this License, for material you
362 | add to a covered work, you may (if authorized by the copyright holders of
363 | that material) supplement the terms of this License with terms:
364 |
365 | a) Disclaiming warranty or limiting liability differently from the
366 | terms of sections 15 and 16 of this License; or
367 |
368 | b) Requiring preservation of specified reasonable legal notices or
369 | author attributions in that material or in the Appropriate Legal
370 | Notices displayed by works containing it; or
371 |
372 | c) Prohibiting misrepresentation of the origin of that material, or
373 | requiring that modified versions of such material be marked in
374 | reasonable ways as different from the original version; or
375 |
376 | d) Limiting the use for publicity purposes of names of licensors or
377 | authors of the material; or
378 |
379 | e) Declining to grant rights under trademark law for use of some
380 | trade names, trademarks, or service marks; or
381 |
382 | f) Requiring indemnification of licensors and authors of that
383 | material by anyone who conveys the material (or modified versions of
384 | it) with contractual assumptions of liability to the recipient, for
385 | any liability that these contractual assumptions directly impose on
386 | those licensors and authors.
387 |
388 | All other non-permissive additional terms are considered "further
389 | restrictions" within the meaning of section 10. If the Program as you
390 | received it, or any part of it, contains a notice stating that it is
391 | governed by this License along with a term that is a further
392 | restriction, you may remove that term. If a license document contains
393 | a further restriction but permits relicensing or conveying under this
394 | License, you may add to a covered work material governed by the terms
395 | of that license document, provided that the further restriction does
396 | not survive such relicensing or conveying.
397 |
398 | If you add terms to a covered work in accord with this section, you
399 | must place, in the relevant source files, a statement of the
400 | additional terms that apply to those files, or a notice indicating
401 | where to find the applicable terms.
402 |
403 | Additional terms, permissive or non-permissive, may be stated in the
404 | form of a separately written license, or stated as exceptions;
405 | the above requirements apply either way.
406 |
407 | 8. Termination.
408 |
409 | You may not propagate or modify a covered work except as expressly
410 | provided under this License. Any attempt otherwise to propagate or
411 | modify it is void, and will automatically terminate your rights under
412 | this License (including any patent licenses granted under the third
413 | paragraph of section 11).
414 |
415 | However, if you cease all violation of this License, then your
416 | license from a particular copyright holder is reinstated (a)
417 | provisionally, unless and until the copyright holder explicitly and
418 | finally terminates your license, and (b) permanently, if the copyright
419 | holder fails to notify you of the violation by some reasonable means
420 | prior to 60 days after the cessation.
421 |
422 | Moreover, your license from a particular copyright holder is
423 | reinstated permanently if the copyright holder notifies you of the
424 | violation by some reasonable means, this is the first time you have
425 | received notice of violation of this License (for any work) from that
426 | copyright holder, and you cure the violation prior to 30 days after
427 | your receipt of the notice.
428 |
429 | Termination of your rights under this section does not terminate the
430 | licenses of parties who have received copies or rights from you under
431 | this License. If your rights have been terminated and not permanently
432 | reinstated, you do not qualify to receive new licenses for the same
433 | material under section 10.
434 |
435 | 9. Acceptance Not Required for Having Copies.
436 |
437 | You are not required to accept this License in order to receive or
438 | run a copy of the Program. Ancillary propagation of a covered work
439 | occurring solely as a consequence of using peer-to-peer transmission
440 | to receive a copy likewise does not require acceptance. However,
441 | nothing other than this License grants you permission to propagate or
442 | modify any covered work. These actions infringe copyright if you do
443 | not accept this License. Therefore, by modifying or propagating a
444 | covered work, you indicate your acceptance of this License to do so.
445 |
446 | 10. Automatic Licensing of Downstream Recipients.
447 |
448 | Each time you convey a covered work, the recipient automatically
449 | receives a license from the original licensors, to run, modify and
450 | propagate that work, subject to this License. You are not responsible
451 | for enforcing compliance by third parties with this License.
452 |
453 | An "entity transaction" is a transaction transferring control of an
454 | organization, or substantially all assets of one, or subdividing an
455 | organization, or merging organizations. If propagation of a covered
456 | work results from an entity transaction, each party to that
457 | transaction who receives a copy of the work also receives whatever
458 | licenses to the work the party's predecessor in interest had or could
459 | give under the previous paragraph, plus a right to possession of the
460 | Corresponding Source of the work from the predecessor in interest, if
461 | the predecessor has it or can get it with reasonable efforts.
462 |
463 | You may not impose any further restrictions on the exercise of the
464 | rights granted or affirmed under this License. For example, you may
465 | not impose a license fee, royalty, or other charge for exercise of
466 | rights granted under this License, and you may not initiate litigation
467 | (including a cross-claim or counterclaim in a lawsuit) alleging that
468 | any patent claim is infringed by making, using, selling, offering for
469 | sale, or importing the Program or any portion of it.
470 |
471 | 11. Patents.
472 |
473 | A "contributor" is a copyright holder who authorizes use under this
474 | License of the Program or a work on which the Program is based. The
475 | work thus licensed is called the contributor's "contributor version".
476 |
477 | A contributor's "essential patent claims" are all patent claims
478 | owned or controlled by the contributor, whether already acquired or
479 | hereafter acquired, that would be infringed by some manner, permitted
480 | by this License, of making, using, or selling its contributor version,
481 | but do not include claims that would be infringed only as a
482 | consequence of further modification of the contributor version. For
483 | purposes of this definition, "control" includes the right to grant
484 | patent sublicenses in a manner consistent with the requirements of
485 | this License.
486 |
487 | Each contributor grants you a non-exclusive, worldwide, royalty-free
488 | patent license under the contributor's essential patent claims, to
489 | make, use, sell, offer for sale, import and otherwise run, modify and
490 | propagate the contents of its contributor version.
491 |
492 | In the following three paragraphs, a "patent license" is any express
493 | agreement or commitment, however denominated, not to enforce a patent
494 | (such as an express permission to practice a patent or covenant not to
495 | sue for patent infringement). To "grant" such a patent license to a
496 | party means to make such an agreement or commitment not to enforce a
497 | patent against the party.
498 |
499 | If you convey a covered work, knowingly relying on a patent license,
500 | and the Corresponding Source of the work is not available for anyone
501 | to copy, free of charge and under the terms of this License, through a
502 | publicly available network server or other readily accessible means,
503 | then you must either (1) cause the Corresponding Source to be so
504 | available, or (2) arrange to deprive yourself of the benefit of the
505 | patent license for this particular work, or (3) arrange, in a manner
506 | consistent with the requirements of this License, to extend the patent
507 | license to downstream recipients. "Knowingly relying" means you have
508 | actual knowledge that, but for the patent license, your conveying the
509 | covered work in a country, or your recipient's use of the covered work
510 | in a country, would infringe one or more identifiable patents in that
511 | country that you have reason to believe are valid.
512 |
513 | If, pursuant to or in connection with a single transaction or
514 | arrangement, you convey, or propagate by procuring conveyance of, a
515 | covered work, and grant a patent license to some of the parties
516 | receiving the covered work authorizing them to use, propagate, modify
517 | or convey a specific copy of the covered work, then the patent license
518 | you grant is automatically extended to all recipients of the covered
519 | work and works based on it.
520 |
521 | A patent license is "discriminatory" if it does not include within
522 | the scope of its coverage, prohibits the exercise of, or is
523 | conditioned on the non-exercise of one or more of the rights that are
524 | specifically granted under this License. You may not convey a covered
525 | work if you are a party to an arrangement with a third party that is
526 | in the business of distributing software, under which you make payment
527 | to the third party based on the extent of your activity of conveying
528 | the work, and under which the third party grants, to any of the
529 | parties who would receive the covered work from you, a discriminatory
530 | patent license (a) in connection with copies of the covered work
531 | conveyed by you (or copies made from those copies), or (b) primarily
532 | for and in connection with specific products or compilations that
533 | contain the covered work, unless you entered into that arrangement,
534 | or that patent license was granted, prior to 28 March 2007.
535 |
536 | Nothing in this License shall be construed as excluding or limiting
537 | any implied license or other defenses to infringement that may
538 | otherwise be available to you under applicable patent law.
539 |
540 | 12. No Surrender of Others' Freedom.
541 |
542 | If conditions are imposed on you (whether by court order, agreement or
543 | otherwise) that contradict the conditions of this License, they do not
544 | excuse you from the conditions of this License. If you cannot convey a
545 | covered work so as to satisfy simultaneously your obligations under this
546 | License and any other pertinent obligations, then as a consequence you may
547 | not convey it at all. For example, if you agree to terms that obligate you
548 | to collect a royalty for further conveying from those to whom you convey
549 | the Program, the only way you could satisfy both those terms and this
550 | License would be to refrain entirely from conveying the Program.
551 |
552 | 13. Use with the GNU Affero General Public License.
553 |
554 | Notwithstanding any other provision of this License, you have
555 | permission to link or combine any covered work with a work licensed
556 | under version 3 of the GNU Affero General Public License into a single
557 | combined work, and to convey the resulting work. The terms of this
558 | License will continue to apply to the part which is the covered work,
559 | but the special requirements of the GNU Affero General Public License,
560 | section 13, concerning interaction through a network will apply to the
561 | combination as such.
562 |
563 | 14. Revised Versions of this License.
564 |
565 | The Free Software Foundation may publish revised and/or new versions of
566 | the GNU General Public License from time to time. Such new versions will
567 | be similar in spirit to the present version, but may differ in detail to
568 | address new problems or concerns.
569 |
570 | Each version is given a distinguishing version number. If the
571 | Program specifies that a certain numbered version of the GNU General
572 | Public License "or any later version" applies to it, you have the
573 | option of following the terms and conditions either of that numbered
574 | version or of any later version published by the Free Software
575 | Foundation. If the Program does not specify a version number of the
576 | GNU General Public License, you may choose any version ever published
577 | by the Free Software Foundation.
578 |
579 | If the Program specifies that a proxy can decide which future
580 | versions of the GNU General Public License can be used, that proxy's
581 | public statement of acceptance of a version permanently authorizes you
582 | to choose that version for the Program.
583 |
584 | Later license versions may give you additional or different
585 | permissions. However, no additional obligations are imposed on any
586 | author or copyright holder as a result of your choosing to follow a
587 | later version.
588 |
589 | 15. Disclaimer of Warranty.
590 |
591 | THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
592 | APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
593 | HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
594 | OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
595 | THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
596 | PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
597 | IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
598 | ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
599 |
600 | 16. Limitation of Liability.
601 |
602 | IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
603 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
604 | THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
605 | GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
606 | USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
607 | DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
608 | PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
609 | EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
610 | SUCH DAMAGES.
611 |
612 | 17. Interpretation of Sections 15 and 16.
613 |
614 | If the disclaimer of warranty and limitation of liability provided
615 | above cannot be given local legal effect according to their terms,
616 | reviewing courts shall apply local law that most closely approximates
617 | an absolute waiver of all civil liability in connection with the
618 | Program, unless a warranty or assumption of liability accompanies a
619 | copy of the Program in return for a fee.
620 |
621 | END OF TERMS AND CONDITIONS
622 |
623 | How to Apply These Terms to Your New Programs
624 |
625 | If you develop a new program, and you want it to be of the greatest
626 | possible use to the public, the best way to achieve this is to make it
627 | free software which everyone can redistribute and change under these terms.
628 |
629 | To do so, attach the following notices to the program. It is safest
630 | to attach them to the start of each source file to most effectively
631 | state the exclusion of warranty; and each file should have at least
632 | the "copyright" line and a pointer to where the full notice is found.
633 |
634 | {one line to give the program's name and a brief idea of what it does.}
635 | Copyright (C) {year} {name of author}
636 |
637 | This program is free software: you can redistribute it and/or modify
638 | it under the terms of the GNU General Public License as published by
639 | the Free Software Foundation, either version 3 of the License, or
640 | (at your option) any later version.
641 |
642 | This program is distributed in the hope that it will be useful,
643 | but WITHOUT ANY WARRANTY; without even the implied warranty of
644 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
645 | GNU General Public License for more details.
646 |
647 | You should have received a copy of the GNU General Public License
648 | along with this program. If not, see .
649 |
650 | Also add information on how to contact you by electronic and paper mail.
651 |
652 | If the program does terminal interaction, make it output a short
653 | notice like this when it starts in an interactive mode:
654 |
655 | {project} Copyright (C) {year} {fullname}
656 | This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
657 | This is free software, and you are welcome to redistribute it
658 | under certain conditions; type `show c' for details.
659 |
660 | The hypothetical commands `show w' and `show c' should show the appropriate
661 | parts of the General Public License. Of course, your program's commands
662 | might be different; for a GUI interface, you would use an "about box".
663 |
664 | You should also get your employer (if you work as a programmer) or school,
665 | if any, to sign a "copyright disclaimer" for the program, if necessary.
666 | For more information on this, and how to apply and follow the GNU GPL, see
667 | .
668 |
669 | The GNU General Public License does not permit incorporating your program
670 | into proprietary programs. If your program is a subroutine library, you
671 | may consider it more useful to permit linking proprietary applications with
672 | the library. If this is what you want to do, use the GNU Lesser General
673 | Public License instead of this License. But first, please read
674 | .
--------------------------------------------------------------------------------