├── 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 | . --------------------------------------------------------------------------------