├── BUILDIDX ├── .clang-format.save ├── cbm ├── loader0 ├── loader1 ├── tbnk4 ├── tbnk5 ├── autoboot.c65 ├── imgconvert └── wrapper.prg ├── devrun.sh ├── maps ├── library.drm ├── outdoor33.drm ├── terrorShack.drm ├── terrorShack.drs ├── outdoor33.drs └── library.drs ├── manual ├── manual.pdf ├── images │ ├── emptyCity.png │ ├── startscreen.png │ └── illustrations │ │ ├── ddungeon.jpg │ │ ├── dormouse.jpg │ │ ├── dragon2.jpg │ │ ├── dulegra.jpg │ │ └── treasure.jpg └── manual.tex ├── requirements.txt ├── src ├── dungeonLoader.h ├── armory.h ├── debug.h ├── debug.c ├── dungeon.h ├── utils.h ├── menu.h ├── encounter.h ├── city.h ├── guild.h ├── config.h ├── spell.h ├── monster.h ├── character.h ├── dispatcher.h ├── utils.c ├── config.c ├── globals.h ├── menu.c ├── dispatcher.c ├── congui.h ├── spell.c ├── monster.c ├── c64.cfg ├── main.c ├── guild.c ├── armory.c ├── dungeonLoader.c ├── types.h ├── city.c └── character.c ├── .clang-format ├── screenshots ├── dr0.png ├── dr1.png ├── dr2.png ├── dr3.png ├── dr4.png ├── dr5.png ├── dr6.png ├── mapEditor.png └── mapCompiler.png ├── graphics ├── dr-charset.bin └── dr-charset.vchar64proj ├── images-src ├── src │ ├── drock.iff │ ├── drock.png │ └── title.xcf ├── ui │ └── borders.png └── artwork │ ├── city1.png │ ├── city4.png │ ├── inn1.png │ └── guild1.png ├── .gitmodules ├── .vscode ├── .ropeproject │ ├── objectdb │ └── config.py ├── settings.json ├── c_cpp_properties.json ├── tasks.json └── launch.json ├── tools ├── buildMaps.sh ├── increaseBuild.sh ├── buildCharset.sh ├── buildResources.sh ├── buildDisc.sh ├── genItems.py ├── png2dbm.py └── genMonsters.py ├── .gitignore ├── DRWorkspace.code-workspace ├── doc ├── TODO.md ├── opcodes.md ├── map.txt └── JITSA.md ├── gamedata-src ├── items.yaml └── monsters.yaml ├── SConstruct ├── setupPythonEnvironment.sh ├── c64.cfg └── README.md /BUILDIDX: -------------------------------------------------------------------------------- 1 | 4877 2 | -------------------------------------------------------------------------------- /.clang-format.save: -------------------------------------------------------------------------------- 1 | clang-format -style=llvm -dump-config > .clang-format 2 | -------------------------------------------------------------------------------- /cbm/loader0: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/steph72/dragonrock-mega65/HEAD/cbm/loader0 -------------------------------------------------------------------------------- /cbm/loader1: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/steph72/dragonrock-mega65/HEAD/cbm/loader1 -------------------------------------------------------------------------------- /cbm/tbnk4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/steph72/dragonrock-mega65/HEAD/cbm/tbnk4 -------------------------------------------------------------------------------- /cbm/tbnk5: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/steph72/dragonrock-mega65/HEAD/cbm/tbnk5 -------------------------------------------------------------------------------- /cbm/autoboot.c65: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/steph72/dragonrock-mega65/HEAD/cbm/autoboot.c65 -------------------------------------------------------------------------------- /cbm/imgconvert: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/steph72/dragonrock-mega65/HEAD/cbm/imgconvert -------------------------------------------------------------------------------- /cbm/wrapper.prg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/steph72/dragonrock-mega65/HEAD/cbm/wrapper.prg -------------------------------------------------------------------------------- /devrun.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | sudo ~/devel/mega65/mega65-tools/bin/m65 -d disc/drock.d81 3 | 4 | -------------------------------------------------------------------------------- /maps/library.drm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/steph72/dragonrock-mega65/HEAD/maps/library.drm -------------------------------------------------------------------------------- /manual/manual.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/steph72/dragonrock-mega65/HEAD/manual/manual.pdf -------------------------------------------------------------------------------- /maps/outdoor33.drm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/steph72/dragonrock-mega65/HEAD/maps/outdoor33.drm -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | SCons>=4.9.0 2 | pyparsing>=3.0.0 3 | PyYAML>=6.0 4 | pypng>=0.20220715.0 5 | -------------------------------------------------------------------------------- /src/dungeonLoader.h: -------------------------------------------------------------------------------- 1 | #include "types.h" 2 | 3 | dungeonDescriptor* loadMap(char *filename); 4 | -------------------------------------------------------------------------------- /.clang-format: -------------------------------------------------------------------------------- 1 | { BasedOnStyle: LLVM, IndentWidth: 4, SpaceBeforeAssignmentOperators: false } 2 | 3 | -------------------------------------------------------------------------------- /maps/terrorShack.drm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/steph72/dragonrock-mega65/HEAD/maps/terrorShack.drm -------------------------------------------------------------------------------- /screenshots/dr0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/steph72/dragonrock-mega65/HEAD/screenshots/dr0.png -------------------------------------------------------------------------------- /screenshots/dr1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/steph72/dragonrock-mega65/HEAD/screenshots/dr1.png -------------------------------------------------------------------------------- /screenshots/dr2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/steph72/dragonrock-mega65/HEAD/screenshots/dr2.png -------------------------------------------------------------------------------- /screenshots/dr3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/steph72/dragonrock-mega65/HEAD/screenshots/dr3.png -------------------------------------------------------------------------------- /screenshots/dr4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/steph72/dragonrock-mega65/HEAD/screenshots/dr4.png -------------------------------------------------------------------------------- /screenshots/dr5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/steph72/dragonrock-mega65/HEAD/screenshots/dr5.png -------------------------------------------------------------------------------- /screenshots/dr6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/steph72/dragonrock-mega65/HEAD/screenshots/dr6.png -------------------------------------------------------------------------------- /graphics/dr-charset.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/steph72/dragonrock-mega65/HEAD/graphics/dr-charset.bin -------------------------------------------------------------------------------- /src/armory.h: -------------------------------------------------------------------------------- 1 | 2 | void initArmory(); 3 | void saveArmory(); 4 | void releaseArmory(); 5 | void doArmory(); -------------------------------------------------------------------------------- /images-src/src/drock.iff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/steph72/dragonrock-mega65/HEAD/images-src/src/drock.iff -------------------------------------------------------------------------------- /images-src/src/drock.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/steph72/dragonrock-mega65/HEAD/images-src/src/drock.png -------------------------------------------------------------------------------- /images-src/src/title.xcf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/steph72/dragonrock-mega65/HEAD/images-src/src/title.xcf -------------------------------------------------------------------------------- /images-src/ui/borders.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/steph72/dragonrock-mega65/HEAD/images-src/ui/borders.png -------------------------------------------------------------------------------- /screenshots/mapEditor.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/steph72/dragonrock-mega65/HEAD/screenshots/mapEditor.png -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "mega65-libc"] 2 | path = mega65-libc 3 | url = https://github.com/MEGA65/mega65-libc.git 4 | -------------------------------------------------------------------------------- /images-src/artwork/city1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/steph72/dragonrock-mega65/HEAD/images-src/artwork/city1.png -------------------------------------------------------------------------------- /images-src/artwork/city4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/steph72/dragonrock-mega65/HEAD/images-src/artwork/city4.png -------------------------------------------------------------------------------- /images-src/artwork/inn1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/steph72/dragonrock-mega65/HEAD/images-src/artwork/inn1.png -------------------------------------------------------------------------------- /manual/images/emptyCity.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/steph72/dragonrock-mega65/HEAD/manual/images/emptyCity.png -------------------------------------------------------------------------------- /screenshots/mapCompiler.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/steph72/dragonrock-mega65/HEAD/screenshots/mapCompiler.png -------------------------------------------------------------------------------- /.vscode/.ropeproject/objectdb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/steph72/dragonrock-mega65/HEAD/.vscode/.ropeproject/objectdb -------------------------------------------------------------------------------- /graphics/dr-charset.vchar64proj: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/steph72/dragonrock-mega65/HEAD/graphics/dr-charset.vchar64proj -------------------------------------------------------------------------------- /images-src/artwork/guild1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/steph72/dragonrock-mega65/HEAD/images-src/artwork/guild1.png -------------------------------------------------------------------------------- /manual/images/startscreen.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/steph72/dragonrock-mega65/HEAD/manual/images/startscreen.png -------------------------------------------------------------------------------- /manual/images/illustrations/ddungeon.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/steph72/dragonrock-mega65/HEAD/manual/images/illustrations/ddungeon.jpg -------------------------------------------------------------------------------- /manual/images/illustrations/dormouse.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/steph72/dragonrock-mega65/HEAD/manual/images/illustrations/dormouse.jpg -------------------------------------------------------------------------------- /manual/images/illustrations/dragon2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/steph72/dragonrock-mega65/HEAD/manual/images/illustrations/dragon2.jpg -------------------------------------------------------------------------------- /manual/images/illustrations/dulegra.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/steph72/dragonrock-mega65/HEAD/manual/images/illustrations/dulegra.jpg -------------------------------------------------------------------------------- /manual/images/illustrations/treasure.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/steph72/dragonrock-mega65/HEAD/manual/images/illustrations/treasure.jpg -------------------------------------------------------------------------------- /src/debug.h: -------------------------------------------------------------------------------- 1 | #include "types.h" 2 | 3 | #ifdef DEBUG 4 | word testMem(void); 5 | #define MEMT testMem() 6 | #else 7 | #define MEMT 8 | #endif -------------------------------------------------------------------------------- /src/debug.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include "types.h" 4 | #include "congui.h" 5 | 6 | word testMem() { 7 | byte *t; 8 | t= (byte *)malloc(0x100); 9 | free(t); 10 | return (word)t; 11 | } -------------------------------------------------------------------------------- /tools/buildMaps.sh: -------------------------------------------------------------------------------- 1 | #! /bin/sh 2 | 3 | set -e 4 | 5 | python3 tools/mc.py maps/library.drs gamedata/map000 6 | python3 tools/mc.py maps/terrorShack.drs gamedata/map001 7 | python3 tools/mc.py maps/outdoor33.drs gamedata/out033 8 | -------------------------------------------------------------------------------- /src/dungeon.h: -------------------------------------------------------------------------------- 1 | #ifndef _dungeonH 2 | #define _dungeonH 3 | 4 | #include "types.h" 5 | 6 | void testMap(void); 7 | void enterDungeonMode(byte reInitMap); 8 | void blitmap(byte mapX, byte mapY, byte posX, byte posY); 9 | void printFeelForIndex(byte idx); 10 | 11 | #endif -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "python.formatting.provider": "autopep8", 3 | "python.linting.enabled": true, 4 | "python.linting.pylintEnabled": true, 5 | "files.associations": { 6 | "conio.h": "c", 7 | "unistd.h": "c", 8 | "c64.h": "c" 9 | } 10 | } -------------------------------------------------------------------------------- /src/utils.h: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | unsigned int drand(unsigned int max); 4 | unsigned int dmrand(unsigned int min, unsigned int max); 5 | unsigned int readExt(FILE *inFile, himemPtr addr, byte skipCBMAddressBytes); 6 | unsigned int loadExt(char *filename, himemPtr addr, byte skipCBMAddressBytes); -------------------------------------------------------------------------------- /tools/increaseBuild.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | 5 | cfile=BUILDIDX 6 | 7 | if [ -f "$cfile" ]; then 8 | echo "incrementing $cfile" 9 | current=`cat $cfile` 10 | current=$[current+1] 11 | echo $current>$cfile 12 | else 13 | echo "creating $cfile" 14 | echo "1">$cfile 15 | fi 16 | 17 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | obj/ 2 | bin/ 3 | gamedata/ 4 | .sconsign.dblite 5 | .vscode/ipch/* 6 | disc/* 7 | dr_venv/ 8 | 9 | .vscode/c_cpp_properties.json 10 | gamedata/* 11 | manual/manual.aux 12 | manual/manual.dvi 13 | manual/manual.fdb_latexmk 14 | manual/manual.fls 15 | manual/manual.log 16 | manual/manual.synctex.gz 17 | dump.mem 18 | -------------------------------------------------------------------------------- /tools/buildCharset.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -e 4 | 5 | # with leading dummy start address suitable to load via cbm kernel 6 | # from the raw charset file 7 | 8 | # dd if=graphics/dr-charset.bin of=bin/drcharset_temp bs=1 count=2048 9 | printf "00" | cat - graphics/dr-charset.bin > bin/drcharset 10 | # rm bin/drcharset_temp 11 | 12 | -------------------------------------------------------------------------------- /src/menu.h: -------------------------------------------------------------------------------- 1 | #ifndef __menuH 2 | #define __menuH 3 | 4 | #include "congui.h" 5 | #include "types.h" 6 | 7 | #define runBottomMenu(A) runMenu(A, 0, 26, 0, 0) 8 | #define runBottomMenuN(A) runMenu(A, 0, 26, 0, 1) 9 | 10 | byte runMenu(char *entries[], byte x, byte y, byte vertical, 11 | byte enableNumberShortcuts); 12 | 13 | #endif -------------------------------------------------------------------------------- /src/encounter.h: -------------------------------------------------------------------------------- 1 | //#include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | 8 | #include "globals.h" 9 | #include "character.h" 10 | #include "congui.h" 11 | #include "monster.h" 12 | 13 | extern monster *gMonsterRows[MONSTER_ROWS][MONSTER_SLOTS]; 14 | 15 | int performAddCoinsOpcode(opcode *anOpcode); 16 | 17 | encResult doEncounter(void); 18 | -------------------------------------------------------------------------------- /src/city.h: -------------------------------------------------------------------------------- 1 | 2 | #ifndef _city 3 | #define _city 4 | 5 | #include "types.h" 6 | 7 | #define getNameForCityID(A) getNameForCityDef(getCityDef(A)) 8 | #define getInnNameForCityID(A) getInnNameForCityDef(getCityDef(A)) 9 | #define getArmorerNameForCityID(A) getArmorerNameForCityDef(getCityDef(A)) 10 | 11 | 12 | void enterCityMode(void); 13 | cityDef *getCityDef(byte cityID); 14 | char *getNameForCityDef(cityDef *def); 15 | char *getInnNameForCityDef(cityDef *def); 16 | char *getArmorerNameForCityDef(cityDef *def); 17 | 18 | #endif 19 | -------------------------------------------------------------------------------- /src/guild.h: -------------------------------------------------------------------------------- 1 | #ifndef __guild 2 | #define __guild 3 | 4 | #include "globals.h" 5 | 6 | void newGuildMember(byte city); 7 | void listGuildMembers(void); 8 | void purgeGuildMember(void); 9 | void addToParty(void); 10 | void dropFromParty(void); 11 | 12 | byte isInParty(byte guildIdx); 13 | 14 | signed char nextFreeGuildSlot(void); 15 | signed char nextFreePartySlot(void); 16 | 17 | void initGuildMem(void); 18 | byte initGuild(void); 19 | byte loadGuild(void); 20 | 21 | void saveGuild(void); 22 | void saveParty(void); 23 | 24 | extern character *guild; 25 | 26 | #endif -------------------------------------------------------------------------------- /src/config.h: -------------------------------------------------------------------------------- 1 | #include "types.h" 2 | #include "dispatcher.h" 3 | 4 | // these may change... 5 | 6 | #define GUILDSIZE 24 7 | #define PARTYSIZE 6 8 | 9 | #define NUM_CITIES 7 10 | #define NUM_CLASSES 6 11 | #define NUM_RACES 5 12 | 13 | extern char *gRaces[NUM_RACES]; 14 | extern char *gRacesS[NUM_RACES]; 15 | extern char *gClasses[NUM_CLASSES]; 16 | extern char *gClassesS[NUM_CLASSES]; 17 | extern char *gAttributes[NUM_ATTRS]; 18 | extern char *gAttributesS[NUM_ATTRS]; 19 | 20 | extern char *gStateDesc[]; 21 | extern spell gSpells[]; 22 | 23 | extern signed char gRaceModifiers[NUM_RACES][6]; 24 | -------------------------------------------------------------------------------- /.vscode/c_cpp_properties.json: -------------------------------------------------------------------------------- 1 | { 2 | "configurations": [ 3 | { 4 | "name": "Linux", 5 | "includePath": [ 6 | "${workspaceFolder}/**", 7 | "/home/lemon/devel/cc65/include/**", 8 | "/usr/share/cc65/include/**", 9 | "/usr/local/share/cc65/include/**" 10 | ], 11 | "defines": [], 12 | "compilerPath": "/usr/local/bin/cc65", 13 | "cStandard": "c11", 14 | "cppStandard": "c++17", 15 | "intelliSenseMode": "gcc-x64" 16 | } 17 | ], 18 | "version": 4 19 | } -------------------------------------------------------------------------------- /tools/buildResources.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -e 4 | 5 | rm -rf gamedata/* 6 | 7 | tools/buildCharset.sh 8 | tools/buildMaps.sh 9 | 10 | python3 tools/genItems.py gamedata-src/items.yaml gamedata/items 11 | python3 tools/genMonsters.py gamedata-src/monsters.yaml gamedata 12 | 13 | # copy ui images with palette 14 | for filename in images-src/ui/*.png; do 15 | python3 tools/png2dbm.py -v $filename gamedata/$(basename $filename .png).dbm 16 | done 17 | 18 | # copy city images and shift palette 19 | for filename in images-src/artwork/*.png; do 20 | python3 tools/png2dbm.py -vr $filename gamedata/$(basename $filename .png).dbm 21 | done -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | // See https://go.microsoft.com/fwlink/?LinkId=733558 3 | // for the documentation about the tasks.json format 4 | "version": "2.0.0", 5 | "tasks": [ 6 | { 7 | "label": "make", 8 | "type": "shell", 9 | "command": "make", 10 | "group": { 11 | "kind": "build", 12 | "isDefault": true 13 | } 14 | }, 15 | { 16 | "label": "run", 17 | "type": "shell", 18 | "command": "make test", 19 | "group": { 20 | "kind": "test", 21 | "isDefault": true 22 | } 23 | } 24 | ] 25 | } -------------------------------------------------------------------------------- /src/spell.h: -------------------------------------------------------------------------------- 1 | #include "globals.h" 2 | 3 | // check whether spell is available for aChar 4 | byte hasSpell(character *aChar, byte spellID); 5 | 6 | // make spell available for aChar 7 | void setHasSpell(character *aChar, byte spellID); 8 | 9 | // return spell for given spell ID 10 | #define spellForSpellID(spellID) gSpells[spellID] 11 | 12 | // return name for given spell 13 | char *nameOfSpell(spell *aSpell); 14 | char *nameOfSpellWithID(byte spellID); 15 | 16 | // cast spell 17 | byte castSpell(character *aChar); 18 | 19 | // determine whether spell takes character destination 20 | byte spellNeedsCharacterDestination(byte spellID); 21 | 22 | // determine whether spell takes row destination 23 | byte spellNeedsRowDestination(byte spellID); 24 | -------------------------------------------------------------------------------- /DRWorkspace.code-workspace: -------------------------------------------------------------------------------- 1 | { 2 | "folders": [ 3 | { 4 | "path": "." 5 | } 6 | ], 7 | "settings": { 8 | "ca65.buildenv": "linux", 9 | "ca65.cc65": "/usr/bin", 10 | "ca65.cl65.target": "c128", 11 | "ca65.emulatorPath": "x128", 12 | "ca65.testenv": "linux", 13 | "ca65.vscodeenv": "linux", 14 | "files.associations": { 15 | "types.h": "c", 16 | "device.h": "c", 17 | "plus4.h": "c", 18 | "cbm264.h": "c", 19 | "conio.h": "c", 20 | "guild.h": "c", 21 | "time.h": "c", 22 | "cbm.h": "c", 23 | "dispatcher.h": "c", 24 | "typeinfo": "c", 25 | "character.h": "c", 26 | "config.h": "c", 27 | "stdlib.h": "c", 28 | "stdio.h": "c", 29 | "pce.h": "c", 30 | "string.h": "c", 31 | "congui.h": "c", 32 | "cstddef": "c", 33 | "menu.h": "c", 34 | "stddef.h": "c", 35 | "em.h": "c", 36 | "istream": "c" 37 | } 38 | } 39 | } -------------------------------------------------------------------------------- /src/monster.h: -------------------------------------------------------------------------------- 1 | //#include 2 | #include 3 | #include 4 | #include 5 | 6 | #include "character.h" 7 | #include "congui.h" 8 | #include "globals.h" 9 | 10 | #define MONSTER_ROWS 3 11 | #define MONSTER_SLOTS 6 12 | #define MROSTER_SIZE 16 13 | 14 | #define monsterDefForMonster(A) monsterDefForID(A->monsterDefID) 15 | #define pluralNameForMonster(A) pluralNameForMonsterID(A->monsterDefID) 16 | 17 | monsterDef *monsterDefForID(unsigned int id); 18 | char *nameForMonsterDef(monsterDef *aDef); 19 | char *nameForMonsterID(unsigned int id); 20 | char *pluralNameForMonsterID(unsigned int id); 21 | char *nameForMonster(monster *aMonster); 22 | byte getNumberOfMonsterAttacks(monster *aMonster); 23 | 24 | void clearMonsters(void); 25 | void addNewMonster(byte monsterID, byte level, byte min, byte max, byte row); 26 | monster* createMonster(unsigned int monsterID, byte level); 27 | 28 | void initMonsterRows(); 29 | 30 | char* pluralname(monsterDef *aMonsterDef); 31 | 32 | -------------------------------------------------------------------------------- /tools/buildDisc.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -e 4 | 5 | if [ ! -f "disc/drock.d81" ]; then 6 | mkdir -p disc 7 | c1541 -format drock,sk d81 disc/drock.d81 8 | fi 9 | 10 | /bin/sh tools/buildResources.sh 11 | 12 | # wrap main into c65-side loader called "main" 13 | cat cbm/wrapper.prg bin/drmain > bin/main 14 | 15 | c1541 <weapon) 35 | #define getArmor(aCharacter) inventoryItemForID(aCharacter->armor) 36 | #define getShield(aCharacter) inventoryItemForID(aCharacter->shield) 37 | 38 | #endif -------------------------------------------------------------------------------- /src/dispatcher.h: -------------------------------------------------------------------------------- 1 | /* 2 | * ===================== 3 | * DragonRock dispatcher 4 | * ===================== 5 | * 6 | * Loading and switching between game module overlays. 7 | * 8 | */ 9 | 10 | #ifndef dispatcher_d 11 | #define dispatcher_d 12 | 13 | #include "globals.h" 14 | 15 | 16 | void loadModules(void); 17 | 18 | 19 | /** 20 | * mainDispatchLoop: 21 | * the main dispatch loop is responsible for loading and 22 | * initializing the various game modules. once a module 23 | * has finished, the dispatcher loads the next module which 24 | * was configured via the prepareForGameMode() function 25 | */ 26 | void mainDispatchLoop(void); 27 | 28 | /** 29 | * prepareNewGameMode: 30 | * signals the dispatcher which new module to load once the current module has ended 31 | */ 32 | void prepareForGameMode(gameModeT newGameMode); 33 | 34 | /** 35 | * popLastGameMode: 36 | * signals the dispatcher to load the last used module 37 | * before the current module became active. used by the 38 | * encounter game mode to get back to whichever game 39 | * mode started the encounter 40 | */ 41 | void popLastGameMode(void); 42 | 43 | /** 44 | * commitNewGameMode loads the prepared game module, 45 | * saves the last active game module and does the 46 | * necessary initializations 47 | */ 48 | void commitNewGameMode(void); // TODO: make this private after debugging is done 49 | 50 | #endif -------------------------------------------------------------------------------- /gamedata-src/items.yaml: -------------------------------------------------------------------------------- 1 | 2 | # nothing 3 | 4 | - [ 0x00, "--", it_special, 0, 0, 0, 0 ] 5 | 6 | # weapons 7 | 8 | - [ 0x01, "Club", it_weapon, 1, 3, 0, 1 ] 9 | - [0x02, "Staff", it_weapon, 1, 3, 0, 5] 10 | - [0x03, "Dagger", it_weapon, 1, 4, 0, 10] 11 | - [0x04, "Short sword", it_weapon, 1, 6, 0, 20] 12 | 13 | # --- bows & slings --- 14 | 15 | - [0x10, "Sling", it_missile, 1, 4, 0, 1] 16 | - [0x20, "Short bow", it_missile, 3, 6, 0, 10] 17 | - [0x21, "Short bow", it_missile, 5, 6, 1, 100] 18 | - [0x22, "Short bow", it_missile, 8, 6, 2, 1000] 19 | - [0x23, "Short bow", it_missile, 12, 6, 3, 5000] 20 | - [0x24, "Short bow", it_missile, 12, 6, 5, 20000] 21 | 22 | # --- shields --- 23 | 24 | - [0x40, "Small shield", it_shield, 0, 2, 0, 10] 25 | - [0x41, "Medium shield", it_shield, 1, 3, 0, 20] 26 | - [0x42, "Large shield", it_shield, 2, 4, 0, 30] 27 | 28 | # --- armor --- 29 | 30 | - [0x80, "Robe", it_armor, 0, 1, 0, 2] 31 | 32 | # --- scrolls and books --- 33 | 34 | - [0xa0, "Scroll", it_scroll, 1, 0, 0, 1] 35 | - [0xa1, "Scroll", it_scroll, 2, 0, 0, 1] 36 | - [0xa2, "Scroll", it_scroll, 3, 0, 0, 1] 37 | - [0xa3, "Scroll", it_scroll, 4, 0, 0, 1] 38 | - [0xa4, "Scroll", it_scroll, 5, 0, 0, 1] 39 | - [0xa5, "Scroll", it_scroll, 6, 0, 0, 1] 40 | - [0xa6, "Scroll", it_scroll, 7, 0, 0, 1] 41 | - [0xa7, "Scroll", it_scroll, 8, 0, 0, 1] 42 | - [0xa8, "Scroll", it_scroll, 9, 0, 0, 1] 43 | - [0xa9, "Scroll", it_scroll, 10, 0, 0, 1] 44 | 45 | # --- special --- 46 | - [0xf0, "Rusty Key", it_special, 0, 0, 0, 1000] 47 | - [0xff, "White Orb", it_special, 0, 0, 0, 1000] -------------------------------------------------------------------------------- /src/utils.c: -------------------------------------------------------------------------------- 1 | #include "globals.h" 2 | #include "memory.h" 3 | #include "types.h" 4 | #include "congui.h" 5 | #include 6 | #include 7 | 8 | unsigned int readExt(FILE *inFile, himemPtr addr, byte skipCBMAddressBytes) { 9 | 10 | unsigned int readBytes; 11 | unsigned int overallRead; 12 | unsigned long insertPos; 13 | 14 | insertPos= addr; 15 | overallRead= 0; 16 | 17 | if (skipCBMAddressBytes) { 18 | fread(drbuf, 1, 2, inFile); 19 | } 20 | 21 | do { 22 | readBytes= fread(drbuf, 1, DRBUFSIZE, inFile); 23 | if (readBytes) { 24 | overallRead+= readBytes; 25 | lcopy((long)drbuf, insertPos, readBytes); 26 | insertPos+= readBytes; 27 | } 28 | } while (readBytes); 29 | 30 | return overallRead; 31 | } 32 | 33 | unsigned int loadExt(char *filename, himemPtr addr, byte skipCBMAddressBytes) { 34 | 35 | FILE *inFile; 36 | word readBytes; 37 | 38 | inFile= fopen(filename, "r"); 39 | readBytes= readExt(inFile, addr, skipCBMAddressBytes); 40 | fclose(inFile); 41 | 42 | if (readBytes==0) { 43 | cg_fatal("0 bytes from %s",filename); 44 | } 45 | 46 | return readBytes; 47 | } 48 | 49 | unsigned int drand(unsigned int max) { 50 | 51 | unsigned int x; 52 | 53 | do { 54 | x= rand(); 55 | } while (x >= (RAND_MAX - RAND_MAX % max)); 56 | 57 | x%= max; 58 | 59 | return x; 60 | } 61 | 62 | unsigned int dmrand(unsigned int min, unsigned int max) { 63 | return min + (drand(max - min)); 64 | } -------------------------------------------------------------------------------- /src/config.c: -------------------------------------------------------------------------------- 1 | #include "globals.h" 2 | #include 3 | 4 | char *gRaces[NUM_RACES]= {"Human", "Dwarf", "Elf", "Half-Elf", "Gnome"}; 5 | char *gRacesS[NUM_RACES]= {"H", "D", "E", "HE", "G"}; 6 | 7 | char *gAttributes[]= {"Strength", "Intelligence", "Wisdom", 8 | "Dexterity", "Constitution", "Charisma"}; 9 | 10 | char *gAttributesS[]= {"STR", "INT", "WIS", "DEX", "CON", "CHR"}; 11 | 12 | char *gStateDesc[]= {"deleted", "down", "asleep", "dead", "ok", "fled"}; 13 | 14 | signed char gRaceModifiers[NUM_RACES][6]= { 15 | {0, 0, 0, 0, 0, 0}, // human 16 | {2, 0, 0, -1, 0, 0}, // dwarf 17 | {-1, 2, 0, 0, -1, 1}, // elf 18 | {0, 1, 0, 0, 0, 0}, // half-elf 19 | {-1, 1, 1, 2, -1, 0} // gnome 20 | }; 21 | 22 | char *gClasses[NUM_CLASSES]= {"Fighter", "Ranger", "Priest", 23 | "Wizard", "Thief", "Monk"}; 24 | char *gClassesS[NUM_CLASSES]= {"FG", "RG", "PR", "WZ", "TH", "MO"}; 25 | 26 | // spells 27 | // name, spellLevel, minLevel, mpNeeded, minVal, maxVal 28 | 29 | spell gSpells[]= { 30 | 31 | /* 0 */ {"--", 0, 0, 0, 0, 0}, // nothing 32 | 33 | /* 1 */ {"Heal", 1, 1, 2, 1, 4}, // heal 1 34 | /* 2 */ {"Heal", 2, 3, 4, 2, 8}, // heal 2 35 | /* 3 */ {"Heal", 3, 6, 8, 4, 10}, // heal 3 36 | /* 4 */ {"Heal", 4, 8, 16, 6, 15}, // heal 4 37 | 38 | /* 5 */ {"Fireflash", 1, 1, 2, 1, 4}, // fireflash 1 39 | /* 6 */ {"Fireflash", 2, 3, 4, 2, 8}, // fireflash 2 40 | /* 7 */ {"Fireflash", 3, 6, 8, 4, 10}, // fireflash 3 41 | /* 8 */ {"Fireflash", 4, 8, 16, 6, 15}, // fireflash 4 42 | 43 | }; 44 | 45 | -------------------------------------------------------------------------------- /src/globals.h: -------------------------------------------------------------------------------- 1 | #include "config.h" 2 | #include "types.h" 3 | 4 | #ifndef DR_GLOBALS 5 | #define DR_GLOBALS 6 | 7 | // memory allocation 8 | 9 | #define SCREENBASE 0x12000l // 16 bit screen 10 | #define EXTCHARBASE 0x13000l // extended characters for map 11 | #define SYSPAL 0x14000 // system palette 12 | #define PALBASE 0x14300l // palettes for loaded images 13 | #define CFG_STORAGE_BASE 0x16000l // base address for high memory storage 14 | #define SEENMAP_BASE 0x18000l // seen map 15 | #define GRAPHBASE 0x40000l // bitmap characters 16 | #define ATTIC_DUNGEON_DATA 0x8000000 // dungeon data (64K) 17 | #define ATTIC_DUNGEON_CODE 0x8010000 // dungeon code overlay (16K, copied when needed) 18 | #define ATTIC_CITY_CODE 0x8014000 // city code (16K) 19 | #define ATTIC_ENCOUNTER_CODE 0x8018000 // encounter code (16K) 20 | 21 | #define COLBASE 0xff80800l // colours 22 | 23 | #define DRBUFSIZE 0xff 24 | 25 | /* ---- 26 | main 27 | ---- */ 28 | 29 | extern char *drbuf; // general purpose buffer 30 | extern byte devmode; // devmode flag 31 | 32 | extern himemPtr itemBase; 33 | extern himemPtr monstersBase; 34 | extern himemPtr citiesBase; 35 | 36 | /* ---------- 37 | dispatcher 38 | ---------- */ 39 | 40 | extern gameModeT gCurrentGameMode; // current game mode 41 | 42 | extern byte 43 | gCurrentDungeonIndex; // current dungeon (==dungeon to enter on mode change) 44 | extern byte gLoadedDungeonIndex; // currently loaded dungeon 45 | extern byte gStartXPos; // landing position for entering new maps 46 | extern byte gStartYPos; 47 | extern byte gCurrentCityIndex; // current city index 48 | extern encResult gEncounterResult; // result of last encounter 49 | 50 | /* --------------- 51 | character/party 52 | --------------- */ 53 | 54 | extern character **party; // current adventuring party 55 | extern long int gPartyGold; // gold to be distributed when entering city 56 | extern long int gPartyExperience; // xp to be distributed when entering city 57 | 58 | /* ------- 59 | dungeon 60 | ------- */ 61 | 62 | extern himemPtr seenMap; // map of seen spaces in dungeon 63 | 64 | #endif -------------------------------------------------------------------------------- /SConstruct: -------------------------------------------------------------------------------- 1 | import os 2 | import shutil 3 | 4 | # Create required directories if they don't exist 5 | for directory in ['obj', 'bin', 'disc', 'gamedata']: 6 | if not os.path.exists(directory): 7 | print(f"Creating directory: {directory}") 8 | os.makedirs(directory) 9 | 10 | env = Environment( 11 | ENV={'PATH': os.environ['PATH']}, 12 | CPPPATH='mega65-libc/cc65/include', 13 | CC='cl65') 14 | 15 | # Configure object files to be stored in obj directory 16 | env.VariantDir('obj', 'src', duplicate=0) 17 | 18 | # Use objects from obj directory 19 | src_files = ['obj/' + os.path.basename(str(f)) for f in Glob("src/*.c")] 20 | lib_files = ['mega65-libc/cc65/src/memory.c'] 21 | 22 | # Compile the program (without running buildDisc.sh) 23 | program = env.Program('bin/drock.c64', 24 | src_files + lib_files, 25 | CCFLAGS='-DDEBUG -Or --cpu 65c02') 26 | 27 | # Create compile alias 28 | compile_cmd = env.Alias('compile', program) 29 | Default(compile_cmd) # Make 'compile' the default when running just 'scons' 30 | 31 | # Create full build command (compile + buildDisc.sh) 32 | buildDiscAction = Action('tools/buildDisc.sh') 33 | build_cmd = env.Command('build', program, buildDiscAction) 34 | env.Alias('build', build_cmd) # Allow 'scons build' to trigger full build 35 | 36 | # Custom clean function 37 | def clean_directories(target, source, env): 38 | dirs_to_clean = ['gamedata', 'bin', 'obj', 'disc'] 39 | for dir_path in dirs_to_clean: 40 | if os.path.exists(dir_path): 41 | print(f"Cleaning directory: {dir_path}") 42 | for item in os.listdir(dir_path): 43 | item_path = os.path.join(dir_path, item) 44 | if os.path.isfile(item_path): 45 | os.unlink(item_path) 46 | elif os.path.isdir(item_path): 47 | shutil.rmtree(item_path) 48 | # Recreate the directory after cleaning 49 | if not os.path.exists(dir_path): 50 | os.makedirs(dir_path) 51 | return 0 52 | 53 | # Add a clean target 54 | clean_cmd = env.Command('clean', None, Action(clean_directories, "Cleaning build directories...")) 55 | env.Alias('clean', clean_cmd) 56 | 57 | # Help text 58 | Help(""" 59 | Available commands: 60 | 'scons' or 'scons compile' - Compile the project only 61 | 'scons build' - Compile the project and build the disc image 62 | 'scons clean' - Clean gamedata, bin, obj, and disc directories 63 | """) 64 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "name": "Python: Current File (Integrated Terminal)", 9 | "type": "python", 10 | "request": "launch", 11 | "program": "${file}", 12 | "console": "integratedTerminal", 13 | "args": [ 14 | "graphics/test.png", 15 | "graphics/out.dbm" 16 | ] 17 | }, 18 | { 19 | "name": "Python: Remote Attach", 20 | "type": "python", 21 | "request": "attach", 22 | "port": 5678, 23 | "host": "localhost", 24 | "pathMappings": [ 25 | { 26 | "localRoot": "${workspaceFolder}", 27 | "remoteRoot": "." 28 | } 29 | ] 30 | }, 31 | { 32 | "name": "Python: Module", 33 | "type": "python", 34 | "request": "launch", 35 | "module": "enter-your-module-name-here", 36 | "console": "integratedTerminal" 37 | }, 38 | { 39 | "name": "Python: Django", 40 | "type": "python", 41 | "request": "launch", 42 | "program": "${workspaceFolder}/manage.py", 43 | "console": "integratedTerminal", 44 | "args": [ 45 | "runserver", 46 | "--noreload", 47 | "--nothreading" 48 | ], 49 | "django": true 50 | }, 51 | { 52 | "name": "Python: Flask", 53 | "type": "python", 54 | "request": "launch", 55 | "module": "flask", 56 | "env": { 57 | "FLASK_APP": "app.py" 58 | }, 59 | "args": [ 60 | "run", 61 | "--no-debugger", 62 | "--no-reload" 63 | ], 64 | "jinja": true 65 | }, 66 | { 67 | "name": "Python: Current File (External Terminal)", 68 | "type": "python", 69 | "request": "launch", 70 | "program": "${file}", 71 | "console": "externalTerminal", 72 | "args": [ 73 | "mapsrc/testmap.drm" 74 | ] 75 | } 76 | ] 77 | } -------------------------------------------------------------------------------- /src/menu.c: -------------------------------------------------------------------------------- 1 | #include "menu.h" 2 | #include 3 | #include 4 | 5 | char **_menuEntries; 6 | long lastMenuChecksum; 7 | 8 | byte _menuEntriesCount; 9 | signed char _menuSelectedEntry; 10 | static byte _mI; 11 | 12 | #define i _mI 13 | 14 | byte refreshMenuH(byte x, byte y) { 15 | cg_gotoxy(x, y); 16 | for (i= 0; i < _menuEntriesCount; ++i) { 17 | cg_revers(_menuSelectedEntry == i); 18 | cg_puts(_menuEntries[i]); 19 | cg_revers(0); 20 | cg_puts(" "); 21 | } 22 | } 23 | 24 | byte refreshMenuV(byte x, byte y) { 25 | for (i= 0; i < _menuEntriesCount; ++i) { 26 | cg_gotoxy(x, y + i); 27 | cg_revers(_menuSelectedEntry == i); 28 | cg_puts(_menuEntries[i]); 29 | cg_revers(0); 30 | cg_puts(" "); 31 | } 32 | } 33 | 34 | #undef i 35 | 36 | byte runMenu(char *entries[], byte x, byte y, byte vertical, 37 | byte enableNumberShortcuts) { 38 | byte quitMenu; 39 | char menuCmd; 40 | long checksum; 41 | char *menuEntry; 42 | 43 | _menuEntries= entries; 44 | _menuEntriesCount= 0; 45 | checksum= 0; 46 | 47 | quitMenu= false; 48 | while (menuEntry= _menuEntries[_menuEntriesCount++]) { 49 | checksum+= (long)menuEntry; 50 | }; 51 | 52 | _menuEntriesCount--; 53 | 54 | if (checksum != lastMenuChecksum) { 55 | _menuSelectedEntry= 0; 56 | lastMenuChecksum= checksum; 57 | } 58 | 59 | if (!vertical) { 60 | cg_block_raw(x, y, gScreenColumns - 1, y, 32, 0); 61 | } 62 | 63 | while (!quitMenu) { 64 | if (vertical) { 65 | refreshMenuV(x, y); 66 | } else { 67 | refreshMenuH(x, y); 68 | } 69 | 70 | menuCmd= cg_getkey(); 71 | 72 | if (menuCmd >= '1' && menuCmd <= '6' && enableNumberShortcuts) { 73 | return 100 + (menuCmd - '1'); 74 | } 75 | 76 | switch (menuCmd) { 77 | case 157: 78 | case 145: 79 | _menuSelectedEntry--; 80 | break; 81 | 82 | case 17: 83 | case 29: 84 | _menuSelectedEntry++; 85 | break; 86 | 87 | case 13: 88 | quitMenu= true; 89 | 90 | default: 91 | break; 92 | } 93 | if (_menuSelectedEntry >= _menuEntriesCount) { 94 | _menuSelectedEntry= 0; 95 | } else if (_menuSelectedEntry < 0) { 96 | _menuSelectedEntry= _menuEntriesCount - 1; 97 | } 98 | } 99 | return _menuSelectedEntry; 100 | } -------------------------------------------------------------------------------- /setupPythonEnvironment.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # DragonRock Python Environment Setup Script 4 | # This script sets up a Python virtual environment and installs required dependencies 5 | 6 | set -e # Exit immediately if a command exits with a non-zero status 7 | 8 | # Colors for output 9 | RED='\033[0;31m' 10 | GREEN='\033[0;32m' 11 | YELLOW='\033[0;33m' 12 | BLUE='\033[0;34m' 13 | NC='\033[0m' # No Color 14 | 15 | ENV_DIR="dr_venv" 16 | REQUIREMENTS_FILE="requirements.txt" 17 | 18 | echo -e "${BLUE}DragonRock Python Environment Setup${NC}" 19 | echo "----------------------------------------" 20 | 21 | 22 | # Check if Python is installed 23 | if ! command -v python3 &> /dev/null; then 24 | echo -e "${RED}Error: Python 3 is not installed or not in PATH${NC}" 25 | echo "Please install Python 3 and try again" 26 | exit 1 27 | fi 28 | 29 | PYTHON_VERSION=$(python3 --version) 30 | echo -e "${GREEN}Found $PYTHON_VERSION${NC}" 31 | 32 | # Check if requirements.txt exists 33 | if [ ! -f "$REQUIREMENTS_FILE" ]; then 34 | echo -e "${RED}Error: $REQUIREMENTS_FILE not found${NC}" 35 | echo "Please ensure the file exists in the current directory" 36 | exit 1 37 | fi 38 | 39 | # Check if virtual environment exists 40 | if [ ! -d "$ENV_DIR" ]; then 41 | echo -e "${YELLOW}Virtual environment not found. Creating...${NC}" 42 | 43 | # Check if venv module is available 44 | if ! python3 -c "import venv" &> /dev/null; then 45 | echo -e "${RED}Error: Python venv module not available${NC}" 46 | echo "Please install the Python venv package for your distribution" 47 | echo "For Ubuntu/Debian: sudo apt-get install python3-venv" 48 | echo "For Fedora: sudo dnf install python3-libs" 49 | exit 1 50 | fi 51 | 52 | # Create virtual environment 53 | python3 -m venv "$ENV_DIR" 54 | if [ $? -ne 0 ]; then 55 | echo -e "${RED}Failed to create virtual environment${NC}" 56 | exit 1 57 | fi 58 | echo -e "${GREEN}Virtual environment created successfully${NC}" 59 | else 60 | echo -e "${GREEN}Found existing virtual environment${NC}" 61 | fi 62 | 63 | # Activate virtual environment 64 | echo "Activating virtual environment..." 65 | # Source the activate script 66 | . "$ENV_DIR/bin/activate" 67 | 68 | if [ $? -ne 0 ]; then 69 | echo -e "${RED}Failed to activate virtual environment${NC}" 70 | exit 1 71 | fi 72 | 73 | # Check if activation was successful 74 | if [ -z "$VIRTUAL_ENV" ]; then 75 | echo -e "${RED}Virtual environment activation failed${NC}" 76 | exit 1 77 | fi 78 | 79 | echo -e "${GREEN}Virtual environment activated successfully${NC}" 80 | 81 | # Install requirements 82 | echo "Installing required packages from $REQUIREMENTS_FILE..." 83 | pip install -r "$REQUIREMENTS_FILE" 84 | 85 | if [ $? -ne 0 ]; then 86 | echo -e "${RED}Failed to install packages${NC}" 87 | exit 1 88 | fi 89 | 90 | echo -e "${GREEN}All packages installed successfully${NC}" 91 | echo 92 | echo -e "${BLUE}Python environment is ready!${NC}" 93 | echo "To manually activate this environment later, run:" 94 | echo -e "${YELLOW}source $ENV_DIR/bin/activate${NC}" 95 | echo 96 | echo "When finished, you can deactivate the environment by typing:" 97 | echo -e "${YELLOW}deactivate${NC}" 98 | 99 | -------------------------------------------------------------------------------- /src/dispatcher.c: -------------------------------------------------------------------------------- 1 | #include "dispatcher.h" 2 | #include 3 | //#include 4 | #include 5 | #include 6 | #include 7 | 8 | #include "globals.h" 9 | #include "city.h" 10 | #include "dungeon.h" 11 | #include "encounter.h" 12 | #include "guild.h" 13 | #include "memory.h" 14 | #include "utils.h" 15 | #include 16 | 17 | extern unsigned int _OVERLAY1_LOAD__[], _OVERLAY1_SIZE__[]; 18 | extern unsigned int _OVERLAY2_LOAD__[], _OVERLAY2_SIZE__[]; 19 | extern unsigned int _OVERLAY3_LOAD__[], _OVERLAY3_SIZE__[]; 20 | 21 | gameModeT gCurrentGameMode; 22 | gameModeT gNextGameMode; 23 | gameModeT lastGameMode; 24 | 25 | byte gCurrentDungeonIndex; 26 | byte gLoadedDungeonIndex; 27 | byte gStartXPos; 28 | byte gStartYPos; 29 | byte gCurrentCityIndex; 30 | 31 | encResult gEncounterResult; 32 | 33 | void prepareForGameMode(gameModeT newGameMode) { gNextGameMode= newGameMode; } 34 | 35 | void popLastGameMode(void) { gNextGameMode= lastGameMode; } 36 | 37 | void loadModules(void) { 38 | loadExt("dungeon", ATTIC_DUNGEON_CODE, true); 39 | loadExt("city", ATTIC_CITY_CODE, true); 40 | loadExt("encounter", ATTIC_ENCOUNTER_CODE, true); 41 | } 42 | 43 | void commitNewGameMode(void) { 44 | 45 | if (gNextGameMode == gCurrentGameMode) { 46 | return; 47 | } 48 | 49 | lastGameMode= gCurrentGameMode; 50 | gCurrentGameMode= gNextGameMode; 51 | 52 | switch (gNextGameMode) { 53 | 54 | case gm_dungeon: 55 | case gm_outdoor: 56 | if (lastGameMode != gm_dungeon && lastGameMode != gm_outdoor) { 57 | cg_bordercolor(COLOR_BLUE); 58 | lcopy(ATTIC_DUNGEON_CODE, (long)_OVERLAY1_LOAD__, 59 | (unsigned int)_OVERLAY1_SIZE__); 60 | } 61 | break; 62 | 63 | case gm_city: 64 | // bordercolor(COLOR_GREEN); 65 | lcopy(ATTIC_CITY_CODE, (long)_OVERLAY2_LOAD__, 66 | (unsigned int)_OVERLAY2_SIZE__); 67 | break; 68 | 69 | case gm_encounter: 70 | cg_bordercolor(COLOR_RED); 71 | lcopy(ATTIC_ENCOUNTER_CODE, (long)_OVERLAY3_LOAD__, 72 | (unsigned int)_OVERLAY3_SIZE__); 73 | break; 74 | 75 | case gm_init: 76 | cg_fatal("gm init"); 77 | break; 78 | 79 | default: 80 | break; 81 | } 82 | } 83 | 84 | void enterCurrentGameMode() { 85 | 86 | switch (gCurrentGameMode) { 87 | 88 | case gm_city: 89 | enterCityMode(); 90 | break; 91 | 92 | case gm_dungeon: 93 | case gm_outdoor: 94 | // make sure that dungeon/map gets re-initialized 95 | // if not coming from an encounter... 96 | enterDungeonMode(lastGameMode != gm_encounter); 97 | break; 98 | 99 | case gm_encounter: 100 | gEncounterResult= doEncounter(); 101 | clearMonsters(); 102 | if (gEncounterResult != encDead) { 103 | popLastGameMode(); 104 | } else { 105 | prepareForGameMode(gm_city); 106 | } 107 | break; 108 | 109 | default: 110 | break; 111 | } 112 | } 113 | 114 | void mainDispatchLoop(void) { 115 | while (gNextGameMode != gm_end) { 116 | // printf("COMMIT GAME MODE %d",gNextGameMode); 117 | // cgetc(); 118 | commitNewGameMode(); 119 | enterCurrentGameMode(); 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /src/congui.h: -------------------------------------------------------------------------------- 1 | 2 | #include "types.h" 3 | 4 | #ifndef __CONGUI 5 | #define __CONGUI 6 | 7 | // --------------- graphics ------------------ 8 | 9 | typedef struct _dbmInfo { 10 | himemPtr baseAdr; 11 | himemPtr paletteAdr; 12 | byte paletteSize; 13 | byte reservedSysPalette; 14 | byte columns; 15 | byte rows; 16 | word size; 17 | } dbmInfo; 18 | 19 | typedef struct _shapeInfo { 20 | byte shapeID; 21 | dbmInfo *shape0; 22 | dbmInfo *shape1; 23 | } shapeInfo; 24 | 25 | typedef struct _textwin { 26 | byte xc; 27 | byte yc; 28 | byte x0; 29 | byte y0; 30 | byte x1; 31 | byte y1; 32 | byte width; 33 | byte height; 34 | } textwin; 35 | 36 | #define cg_clearxy(x0, y, x1) cg_line(y, x0, x1, 32, 0) 37 | 38 | #define cg_bordercolor(C) POKE(0xd020u,C) 39 | #define cg_bgcolor(C) POKE(0xd021u,C) 40 | 41 | extern byte gScreenColumns; // number of screen columns (in characters) 42 | extern byte gScreenRows; // number of screen rows (in characters) 43 | 44 | // --- general & initializations --- d 45 | 46 | void cg_init(void); 47 | void cg_go16bit(byte h640, byte v400); 48 | void cg_go8bit(); 49 | void cg_fatal(const char *format, ...); 50 | 51 | 52 | // --- borders, columns and titles --- 53 | 54 | void cg_titlec(byte tcol, byte splitScreen, char *t); 55 | void cg_borders(byte showSubwin); 56 | void cg_block_raw(byte x0, byte y0, byte x1, byte y1, byte character, byte col); 57 | void cg_frame(byte x0, byte y0, byte x1, byte y1); 58 | void cg_hlinexy(byte x0, byte y, byte x1, byte secondary); 59 | void cg_vlinexy(byte x, byte y0, byte y1); 60 | void cg_line(byte y, byte x0, byte x1, byte character, byte col); 61 | 62 | 63 | // --- keyboard input --- 64 | 65 | void cg_emptyBuffer(void); 66 | char cg_getkey(void); 67 | char cg_getkeyP(byte x, byte y, const char *prompt); 68 | char *cg_input(byte maxlen); 69 | int cg_getnum(byte maxlen); 70 | 71 | // --- string and character output --- 72 | 73 | void cg_clrscr(); 74 | void cg_putc(char c); 75 | void cg_puts(const char *s); 76 | void cg_putsxy(byte x, byte y, char *s); 77 | void cg_putcxy(byte x, byte y, char c); 78 | void cg_printf(const char *format, ...); 79 | void cg_gotoxy(byte x, byte y); 80 | byte cg_wherex(); 81 | byte cg_wherey(); 82 | void cg_setwin(byte x0, byte y0, byte width, byte height); 83 | void cg_resetwin(); 84 | void cg_pushWin(); 85 | void cg_popWin(); 86 | void cg_cursor(byte onoff); 87 | void cg_center(byte x, byte y, byte width, char *text); 88 | unsigned char cg_cgetc(void); 89 | 90 | // colour and palette handling 91 | 92 | 93 | void cg_revers(byte r); 94 | void cg_flash(byte f); 95 | void cg_textcolor(byte c); 96 | void cg_setPalette(byte num, byte red, byte green, byte blue); 97 | void cg_resetPalette(); 98 | void cg_loadPalette(himemPtr adr, byte size, byte reservedSysPalette); 99 | 100 | // status line stuff 101 | void cg_clearLower(byte num); 102 | void cg_clearBottomLine(); 103 | void cg_displayErrorStatus(char *msg); 104 | void cg_clearFromTo(byte start, byte end); 105 | 106 | void cg_freeGraphAreas(void); 107 | void cg_addGraphicsRect(byte x0, byte y0, byte width, byte height, 108 | himemPtr bitmapData); 109 | dbmInfo *cg_loadDBM(char *filename, himemPtr address, himemPtr paletteAddress); 110 | void cg_displayDBMInfo(dbmInfo *info, byte x0, byte y0); 111 | dbmInfo *cg_displayDBMFile(char *filename, byte x0, byte y0); 112 | void cg_plotExtChar(byte x, byte y, byte c); 113 | void cg_test(); 114 | 115 | #endif -------------------------------------------------------------------------------- /src/spell.c: -------------------------------------------------------------------------------- 1 | #include "spell.h" 2 | #include "globals.h" 3 | #include "utils.h" 4 | #include "congui.h" 5 | //#include 6 | #include 7 | 8 | static byte spellMapByteIdx; 9 | static byte spellMapBitIdx; 10 | 11 | byte hasSpell(character *aChar, byte spellID) { 12 | spellMapByteIdx= spellID / 8; 13 | spellMapBitIdx= spellID % 8; 14 | return aChar->spellMap[spellMapByteIdx] & (1 << (spellMapBitIdx)); 15 | } 16 | 17 | void setHasSpell(character *aChar, byte spellID) { 18 | spellMapByteIdx= spellID / 8; 19 | spellMapBitIdx= spellID % 8; 20 | aChar->spellMap[spellMapByteIdx]|= (1 << spellMapBitIdx); 21 | } 22 | 23 | char *nameOfSpell(spell *aSpell) { 24 | if (aSpell->spellLevel == 0) { 25 | return aSpell->name; 26 | } 27 | sprintf(drbuf, "%s %d", aSpell->name, aSpell->spellLevel); 28 | return drbuf; 29 | } 30 | 31 | char *nameOfSpellWithID(byte spellID) { return nameOfSpell(&gSpells[spellID]); } 32 | 33 | byte isHealingSpell(byte spellID) { 34 | return (spellID >= 1 && spellID <= 4); // healing spell 35 | } 36 | 37 | byte spellNeedsRowDestination(byte spellID) { 38 | return (spellID >= 5 && spellID <= 8); // fireflash spell 39 | } 40 | 41 | byte spellNeedsCharacterDestination(byte spellID) { 42 | return isHealingSpell(spellID); 43 | } 44 | 45 | void announceSpell(character *aChar) { 46 | cg_printf("%s casts %s\n", aChar->name, nameOfSpellWithID(aChar->encSpell)); 47 | } 48 | 49 | // clang-format off 50 | #pragma code-name(push, "OVERLAY3"); 51 | // clang-format on 52 | 53 | byte castFireflashSpell(character *aCharacter) { 54 | byte dmgVal; 55 | spell *aSpell; 56 | 57 | aSpell= &gSpells[aCharacter->encSpell]; 58 | announceSpell(aCharacter); 59 | dmgVal= dmrand(aSpell->minDmg, aSpell->maxDmg); 60 | return true; 61 | } 62 | 63 | // clang-format off 64 | #pragma code-name(pop); 65 | // clang-format on 66 | 67 | byte castHealingSpell(character *srcCharacter) { 68 | byte healVal; 69 | character *destCharacter; 70 | spell *aSpell; 71 | 72 | destCharacter= party[srcCharacter->encDestination - 1]; 73 | aSpell= &gSpells[srcCharacter->encSpell]; 74 | 75 | announceSpell(srcCharacter); 76 | 77 | healVal= dmrand(aSpell->minDmg, aSpell->maxDmg); 78 | destCharacter->aHP+= healVal; 79 | 80 | if (destCharacter->aMaxHP > destCharacter->aMaxHP) { 81 | destCharacter->aHP= destCharacter->aMaxHP; 82 | } 83 | destCharacter->aHP+= healVal; 84 | cg_printf("%s is healed.", destCharacter->name); 85 | if (destCharacter->status == down && destCharacter->aHP > 0) { 86 | cg_printf("\n%s gets up again!", destCharacter->name); 87 | destCharacter->status= awake; 88 | } 89 | return true; 90 | } 91 | 92 | byte castSpell(character *aChar) { 93 | 94 | byte castSuccessful= false; 95 | 96 | if (aChar->encSpell == 0) { 97 | return false; 98 | } 99 | switch (aChar->encSpell) { 100 | case 1: 101 | case 2: 102 | case 3: 103 | case 4: 104 | castSuccessful= castHealingSpell(aChar); 105 | break; 106 | 107 | case 5: 108 | case 6: 109 | case 7: 110 | case 8: 111 | if (gCurrentGameMode==gm_encounter) { 112 | castSuccessful= castFireflashSpell(aChar); 113 | } else { 114 | puts("only in encounter!"); 115 | cg_getkey(); 116 | } 117 | break; 118 | 119 | default: 120 | break; 121 | } 122 | 123 | if (castSuccessful) { 124 | aChar->aMP-= gSpells[aChar->encSpell].mpNeeded; 125 | } 126 | 127 | return castSuccessful; 128 | } 129 | -------------------------------------------------------------------------------- /gamedata-src/monsters.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | cities: 4 | 5 | - cityName: "Foxhome" 6 | innName: "Tia's Paws" 7 | armoryOwnerName: "Ulfric Fodor" 8 | id: 0x00 9 | mapNr: 33 10 | x: 21 11 | y: 15 12 | 13 | - cityName: "King's Winter" 14 | innName: "The Happy Drunk" 15 | armoryOwnerName: "Arnold Hompart" 16 | id: 0x01 17 | mapNr: 33 18 | x: 21 19 | y: 15 20 | 21 | - cityName: "Dohlem" 22 | innName: "The Happy Drunk" 23 | armoryOwnerName: "Arnold Hompart" 24 | id: 0x02 25 | mapNr: 33 26 | x: 21 27 | y: 15 28 | 29 | - cityName: "Tia's Grove" 30 | innName: "The Happy Drunk" 31 | armoryOwnerName: "Arnold Hompart" 32 | id: 0x03 33 | mapNr: 33 34 | x: 21 35 | y: 15 36 | 37 | - cityName: "Modder" 38 | innName: "The Happy Drunk" 39 | armoryOwnerName: "Arnold Hompart" 40 | id: 0x04 41 | mapNr: 33 42 | x: 21 43 | y: 15 44 | 45 | - cityName: "Foodim" 46 | innName: "The Happy Drunk" 47 | armoryOwnerName: "Arnold Hompart" 48 | id: 0x05 49 | mapNr: 33 50 | x: 21 51 | y: 15 52 | 53 | - cityName: "Splitwater" 54 | innName: "The Happy Drunk" 55 | armoryOwnerName: "Arnold Hompart" 56 | id: 0x06 57 | mapNr: 33 58 | x: 21 59 | y: 15 60 | 61 | # ----------------------------------------------------------------------------- 62 | 63 | monsters: 64 | 65 | - id: 0x00 66 | defaultLevel: 1 67 | spriteID: 0x10 68 | monsterType: [mt_humanoid] 69 | name: "Noob" 70 | pluralName: "Noobs" 71 | AC: 10 72 | hitPoints: 6 73 | magPoints: 0 74 | courageMod: 0 75 | attackTypes: [at_weapon] 76 | minDamage: [1] 77 | maxDamage: [4] 78 | hitModifier: [0] 79 | xpValue: 1 80 | 81 | - id: 0x01 82 | defaultLevel: 1 83 | spriteID: 0x11 84 | monsterType: [mt_humanoid] 85 | name: "Kobold" 86 | AC: 9 87 | hitPoints: 6 88 | magPoints: 0 89 | courageMod: -1 90 | attackTypes: [at_weapon] 91 | minDamage: [1] 92 | maxDamage: [5] 93 | hitModifier: [0] 94 | xpValue: 10 95 | 96 | - id: 0x02 97 | defaultLevel: 1 98 | spriteID: 0x11 99 | monsterType: [mt_humanoid] 100 | name: "Pig rider" 101 | AC: 9 102 | hitPoints: 6 103 | magPoints: 0 104 | numAttacks: 1 105 | courageMod: 0 106 | attackTypes: [at_weapon] 107 | minDamage: [1] 108 | maxDamage: [4] 109 | hitModifier: [0] 110 | xpValue: 2 111 | 112 | - id: 0xa0 113 | defaultLevel: 2 114 | spriteID: 0xa0 115 | monsterType: [mt_humanoid,mt_unique] 116 | name: "Lukasz" 117 | AC: 8 118 | hitPoints: 6 119 | magPoints: 0 120 | courageMod: 5 121 | attackTypes: [at_fists,at_weapon] 122 | minDamage: [1,1] 123 | maxDamage: [3,4] 124 | hitModifier: [0,0] 125 | xpValue: 20 126 | 127 | - id: 0xc0 128 | defaultLevel: 10 129 | spriteID: 0xc0 130 | monsterType: [mt_magical,mt_unique] 131 | name: "Obezna, Dragon of Mistrust" 132 | AC: 3 133 | hitPoints: 12 134 | magPoints: 14 135 | courageMod: 2 136 | attackTypes: [at_claws, at_claws, at_ice] 137 | minDamage: [1,1,2] 138 | maxDamage: [6,6,12] 139 | hitModifier: [1,1,0] 140 | spellClass: [sc_battlemage] 141 | xpValue: 900 -------------------------------------------------------------------------------- /doc/opcodes.md: -------------------------------------------------------------------------------- 1 | # DragonRock Opcodes 2 | 3 | ## 0x00 NOP / GOTO <01/02:destOpcodeIndex> 4 | No operation 5 | 6 | ## 0x40 GOTO <01/02:destOpcodeIndex> 7 | Continue execution at *destOpcodeIndex* 8 | 9 | ## 0x01 NSTAT <01:msgID> 10 | Change current status message to *msgID* 11 | 12 | ## 0x21 NSTAT_O <01:msgID> 13 | Like NSTAT, but clear status immediately after player moves on 14 | 15 | ## 0x02 DISP <01:msgID> <02:clrFlag> 16 | Display *msgID* 17 | 18 | ## 0x22 DISP_S <01:msgID> 19 | Display *msgID* in status area (always clears status area first) 20 | 21 | ## 0x03 WKEY <01:msgID> <02:clrFlag> <03:regNr> 22 | Wait for keypress and display msgID. Stores pressed key in *regNr* 23 | 24 | ## 0x04 YESNO <01/02:trueOpcIdx> <03/04:falseOpcodeIdx> 25 | Wait for 'y' or 'n' keypress 26 | Register 0 -> true on 'yes', otherwise false. 27 | if *y* and *trueOpcIdx*!=0 -> call subroutine at *trueOpcIdx* 28 | if *n* and *falseOpcIdx*!=0 -> call subroutine at *falseOpcodeIdx* 29 | 30 | ## 0x44 YESNO_B <01/02:trueOpcIdx> <03/04:falseOpcodeIdx> 31 | Like YESNO, but **branch** to trueOpcIdx instead of calling it 32 | 33 | ## 0x05 IFREG <01:regNr> <02:regValue> <03/04:trueOpcIdx> <05/06:falseOpcIdx> 34 | If register *regNr* contains *regValue*, call subroutine at *trueOpcIdx*, else *falseOpcIdx* 35 | 36 | ## 0x45 IFREG_B <01:regNr> <02:regValue> <03/04:trueOpcIdx> 37 | Like IFREG, but **branch** to trueOpcIdx instead of calling it 38 | 39 | ## 0x06 IFPOS <01:itemId> <02:resultReg> <03/04:trueOpcIdx> <05/06:falseOpcIdx> 40 | If *itemId* is in current party's posession, perform *trueOpcIdx*, else *falseOpcIdx* 41 | Register #resultReg -> party member who is owner of itemID or 255 for not found 42 | 43 | ## 0x07 IADD <01:itemId> <02:charIdx> <03/04:successOpcIdx> <05/06:failureOpcIdx> <07:verboseFlag> 44 | Add *itemId* to character *charIdx* inventory 45 | If *charIdx*==0xff use first free character if posssible 46 | If *verboseFlag* != 0, print '[characterName] took [itemName]' after successful completion 47 | On success, performs ; otherwise 48 | Register 0 -> true on success, otherwise false 49 | Register 1 -> party member who took the item 50 | 51 | ## 0x08 ALTER <01:xpos> <02:ypos> <03:posOpcodeLabel> <04:dungeonItemID> 52 | Alter map at coordinates *xpos*,*ypos* to opcode index pointed to by *posOpcodeLabel* and *dungeonItemID*. WARNING! DungeonItemID has to carry bits 9+10 of the final dungeon item ID address just like a "regular" map entry. 53 | The map compiler automatically builds xpos,ypos from any defined (defc) label. 54 | 55 | ## 0x09 REDRAW 56 | Force redraw the dungeon display 57 | 58 | ## 0x0a ADDC <01:numCoinsL> <02:numCoinsH> <07:verboseFlag> 59 | Give numCoins to the party 60 | If *verboseFlag* != 0, output 'coins taken' message 61 | 62 | ## 0x0b ADDE <01:numExpL> <02:numExpH> <07: verboseFlag> 63 | Give numExp experience to party 64 | If *verboseFlag* != 0, output 'exp taken' message 65 | 66 | ## 0x0c SETREG <01:regNum> <02:regVal> 67 | Set register *regNum* to value *regVal* 68 | 69 | ## 0x0d CLRENC 70 | Clear encounter list 71 | 72 | ## 0x0e ADDENC <01:mID> <02:mLvl> <03:minCount> <04:maxCount> <05:row> 73 | Add to monsters with monster ID of level to encounter row 74 | 75 | ## 0x0f GOENC <01/02:winOpcIdx> <03/04:loseOpcIdx> 76 | Start encounter 77 | 78 | ## 0x10 ENTER_W <01:mapId> <02:xpos> <03:ypos> 79 | Enter wilderness map *mapID* at coords *xpos*, *ypos* and switch to outdoor mode 80 | 81 | ## 0x30 ENTER_D <01:dungeonId> <02:xpos> <03:ypos> 82 | Enter dungeon *dungeonId* at coords *xpos*, *ypos* and switch to dungeon mode 83 | 84 | ## 0x11 ENTER_C <01:cityId> 85 | Enter city *cityId* and switch to city mode 86 | 87 | ## 0x51 RANDOM_B <01/02:randomChance> <03/04:opcIdx> 88 | Jump to *opcIdx* if *randomChance* is greater than a random value between 0 and 999 89 | 90 | # Outdoor and dungeon maps 91 | Outdoor maps are registered by setting bit 7 in their id (i.e. "128+mapnum") 92 | -------------------------------------------------------------------------------- /tools/genItems.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | 3 | import sys 4 | import pickle 5 | import csv 6 | import yaml 7 | 8 | types = ["it_armor", 9 | "it_shield", 10 | "it_weapon", 11 | "it_missile", 12 | "it_potion", 13 | "it_scroll", 14 | "it_special"] 15 | 16 | 17 | def checkFormat(aRow): 18 | if len(aRow) != 7: 19 | return False, "7 columns expected but got "+str(len(aRow)) 20 | type = aRow[2] 21 | if not type in types: 22 | return False, "Unknown type '"+type+"'" 23 | return True, "" 24 | 25 | 26 | def read(aFilename): 27 | with open(aFilename, 'r') as stream: 28 | try: 29 | itemRows = yaml.safe_load(stream) 30 | except yaml.YAMLError as exc: 31 | print(exc) 32 | return itemRows 33 | 34 | 35 | def buildDescriptions(src): 36 | offsets = [] 37 | destbytes = bytearray() 38 | startMarker = "STRINGS*" 39 | destbytes.extend(map(ord, startMarker)) 40 | currentOffset = len(startMarker) 41 | for i in src: 42 | offsets.append(currentOffset) 43 | commobytes = bytearray() 44 | unixbytes = bytearray() 45 | i = i.replace("&&nl&&", "\n") 46 | unixbytes.extend(map(ord, i.swapcase())) 47 | for p in unixbytes: # lf -> crlf 48 | currentOffset += 1 49 | if (p == 10): 50 | commobytes.append(13) 51 | else: 52 | commobytes.append(p) 53 | currentOffset += 1 54 | commobytes.append(0) 55 | destbytes.extend(commobytes) 56 | return destbytes, offsets 57 | 58 | 59 | def rowsToData(srcRows): 60 | descriptions = [] 61 | items = [] 62 | for i in srcRows: 63 | res,err = checkFormat(i) 64 | if (res==False): 65 | print (err+" in",i) 66 | exit(1) 67 | anItem = {} 68 | itemID = i[0] 69 | if itemID in items: 70 | print("Error: Duplicate item ID "+i[0]) 71 | exit(1) 72 | desc = i[1] 73 | if not desc in descriptions: 74 | descriptions.append(desc) 75 | anItem["id"] = itemID 76 | anItem["descriptionIndex"] = descriptions.index(desc) 77 | anItem["type"] = types.index(i[2]) 78 | anItem["val1"] = int(i[3]) 79 | anItem["val2"] = int(i[4]) 80 | anItem["val3"] = int(i[5]) 81 | anItem["xp"] = int(i[6]) 82 | items.append(anItem) 83 | 84 | descbytes, offsets = buildDescriptions(descriptions) 85 | 86 | # replace desc index with offset 87 | # assuming one item definition = 10 bytes 88 | 89 | itemMarker = "DRITEMS0" 90 | 91 | stringsBase = len(itemMarker)+(len(items)*10) 92 | print("Strings base is", hex(stringsBase)) 93 | 94 | outbytes = bytearray() 95 | outbytes.extend(map(ord, itemMarker)) 96 | 97 | for theItem in items: 98 | theItem["descOffset"] = stringsBase + \ 99 | offsets[theItem["descriptionIndex"]] 100 | outbytes.append(theItem["id"] % 256) # 0 101 | outbytes.append(theItem["id"]//256) 102 | outbytes.append(theItem["descOffset"] % 256) # 2 103 | outbytes.append(theItem["descOffset"]//256) 104 | outbytes.append(theItem["type"]) # 4 105 | outbytes.append(theItem["val1"]) # 5 106 | outbytes.append(theItem["val2"]) # 6 107 | outbytes.append(theItem["val3"]) # 7 108 | outbytes.append(theItem["xp"] % 256) # 8 109 | outbytes.append(theItem["xp"]//256) 110 | outbytes.extend(descbytes) 111 | 112 | # print(offsets) 113 | # print(descbytes) 114 | # print(outbytes) 115 | 116 | return outbytes 117 | 118 | print("DragonRock item builder v0.1, (w) Stephan Kleinert, 2021/06") 119 | 120 | if len(sys.argv) < 3: 121 | print("usage: "+sys.argv[0]+" infile outfile") 122 | exit(1) 123 | 124 | srcFilename = sys.argv[1] 125 | destFilename = sys.argv[2] 126 | 127 | itemRows = read(srcFilename) 128 | itemData = rowsToData(itemRows) 129 | 130 | outfile = open(destFilename, "wb") 131 | outfile.write(itemData) 132 | print("Items file written successfully.") 133 | outfile.close() 134 | -------------------------------------------------------------------------------- /src/monster.c: -------------------------------------------------------------------------------- 1 | #include "monster.h" 2 | #include "memory.h" 3 | #include "utils.h" 4 | 5 | monster *gMonsterRows[MONSTER_ROWS][MONSTER_SLOTS]; 6 | 7 | char monsterNameBuf[32]; 8 | 9 | monsterDef tempDef; 10 | 11 | // clang-format off 12 | #pragma code-name(push, "OVERLAY3"); 13 | // clang-format on 14 | 15 | byte getNumberOfMonsterAttacks(monster *aMonster) { 16 | byte i; 17 | byte num = 0; 18 | monsterDef *def = monsterDefForMonster(aMonster); 19 | for (i=0;i<4;++i) { 20 | if (def->aType[i]!=0) { 21 | num++; 22 | } 23 | } 24 | return num; 25 | } 26 | 27 | char *nameForMonsterDef(monsterDef *aDef) { 28 | lcopy((long)monstersBase + (aDef->namePtr), (long)monsterNameBuf, 32); 29 | return monsterNameBuf; 30 | } 31 | 32 | char *pluralNameForMonsterDef(monsterDef *aDef) { 33 | lcopy((long)monstersBase + (aDef->pluralnamePtr), (long)monsterNameBuf, 32); 34 | return monsterNameBuf; 35 | } 36 | 37 | char *nameForMonsterID(unsigned int id) { 38 | return nameForMonsterDef(monsterDefForID(id)); 39 | } 40 | 41 | char *nameForMonster(monster *aMonster) { 42 | return nameForMonsterID(aMonster->monsterDefID); 43 | } 44 | 45 | char *pluralNameForMonsterID(unsigned int id) { 46 | monsterDef *aDef= monsterDefForID(id); 47 | if (aDef->pluralnamePtr) { 48 | pluralNameForMonsterDef(aDef); 49 | } else { 50 | nameForMonsterDef(aDef); 51 | strcat(monsterNameBuf,"s"); 52 | } 53 | return monsterNameBuf; 54 | } 55 | 56 | // clang-format off 57 | #pragma code-name(pop); 58 | // clang-format on 59 | 60 | // -------------------------- common code ---------------------------------- 61 | 62 | monsterDef *monsterDefForID(unsigned int id) { 63 | unsigned int i; 64 | for (i= 0; i < 512; ++i) { 65 | lcopy((long)monstersBase + 8 + (sizeof(monsterDef) * i), 66 | (long)&tempDef, sizeof(monsterDef)); 67 | if (tempDef.id == id) { 68 | return &tempDef; 69 | } 70 | } 71 | return NULL; 72 | } 73 | 74 | void _initMonsterRows(byte dealloc) { 75 | byte i, j; 76 | for (i= 0; i < MONSTER_ROWS; ++i) { 77 | for (j= 0; j < MONSTER_SLOTS; ++j) { 78 | if (dealloc && gMonsterRows[i][j]) { 79 | free(gMonsterRows[i][j]); 80 | } 81 | gMonsterRows[i][j]= NULL; 82 | } 83 | } 84 | } 85 | 86 | void initMonsterRows() { _initMonsterRows(false); } 87 | 88 | // add monster to row 89 | void addMonster(monster *aMonster, byte row) { 90 | byte i; 91 | for (i= 0; i < MONSTER_SLOTS; ++i) { 92 | if (gMonsterRows[row][i] == NULL) { 93 | gMonsterRows[row][i]= aMonster; 94 | aMonster->row= row; 95 | aMonster->column= i; 96 | return; 97 | } 98 | } 99 | cg_fatal("nms"); 100 | } 101 | 102 | // clear monsters 103 | void clearMonsters(void) { _initMonsterRows(true); } 104 | 105 | // create a monster with given ID and level 106 | // (creates standard level if level==0) 107 | 108 | monster *createMonster(unsigned int monsterID, byte level) { 109 | 110 | byte i; 111 | monster *newMonster; 112 | monsterDef *aDef; 113 | 114 | aDef= monsterDefForID(monsterID); 115 | 116 | if (aDef == NULL) { 117 | cg_fatal("invm &d",monsterID); 118 | } 119 | 120 | newMonster= malloc(sizeof(monster)); 121 | 122 | if (level == 0) { 123 | level= aDef->level; 124 | } 125 | 126 | newMonster->hp= 0; 127 | newMonster->mp= 0; 128 | 129 | newMonster->monsterDefID= monsterID; 130 | for (i= 0; i < level; i++) { 131 | newMonster->hp+= drand(aDef->hpPerLevel) + 1; 132 | newMonster->mp+= drand(aDef->mpPerLevel) + 1; 133 | } 134 | newMonster->level= level; 135 | 136 | return newMonster; 137 | } 138 | 139 | // add new monster to row 140 | void addNewMonster(byte monsterID, byte level, byte min, byte max, byte row) { 141 | byte i; 142 | byte num; 143 | monster *theMonster; 144 | num = max==min ? max : min + (drand(max - min)); 145 | for (i= 0; i < num; ++i) { 146 | theMonster= createMonster(monsterID, level); 147 | theMonster->status= awake; /* TODO */ 148 | addMonster(theMonster, row); 149 | } 150 | } -------------------------------------------------------------------------------- /doc/map.txt: -------------------------------------------------------------------------------- 1 | ** memory map ** 2 | 3 | 7bcf 4 | 5 | BANK 0 6 | 0x00400 - 0x004ff drbuf universal buffer 7 | 0x00500 - 0x0050f DMA list 8 | 0x00510 - 0x005af outbuf text output buffer for congui.c 9 | 0x005b0 - 0x005cf gParty party member pointers 10 | 0x005d0 - 0x005ff temp item strings 11 | 0x00600 - 0x006ff window list 12 | 13 | 0x00801 - 0x08fff main program 36k 14 | 0x09000 - 0x0cfff overlays 16k 15 | 16 | BANK 1 17 | 0x12000 - 0x12fff 16 bit screen 18 | 0x13000 - 0x13fff map and borders charset 19 | 0x14000 - 0x142ff system palette 20 | 0x14300 - 0x15fff palettes for loaded images 21 | 0x16000 - 0x17fff game configuration 22 | 0x18000 - 0x1f000 seenmap 23 | 24 | BANK 4,5: 25 | 0x40000 - 0x5ffff bitmap storage 26 | 27 | ATTIC RAM 28 | 0800.0000 - 0800-ffff map & dungeon script storage 64k 29 | 0801.0000 - 0801.3fff dungeon code 16k 30 | 0801.4000 - 0801.7fff city code 16k 31 | 0801.8000 - 0801.bfff encounter code 16k 32 | 0802.0000 - 0802.0fff items 4k 33 | 34 | ** dragon rock map format ** 35 | 36 | +-------------------------------------+ 37 | |= DR0 ===============================| Mapdata segment header 38 | | 0-2 C "DR0" | 39 | | 3-4 W size of map data | 40 | | 5 B map width | 41 | | 6 B map height | 42 | +-------------------------------------+ 43 | | 0 B map element 1 | mapdata 44 | | 0-4 : element type | 45 | | 5 : space impassable | 46 | | 6 : bit 8 of opcAdr | 47 | | 7 : bit 9 of opcAdr | 48 | | | 49 | | 1 B bits 0-7 of opcAdr | 50 | | | 51 | +-------------------------------------+ 52 | | 0 B map element 2 | 53 | | [...] | 54 | +-------------------------------------+ 55 | |= FEELS =============================| string table segment header 56 | | 0-4 C "FEELS" | 57 | | 5 W number of strings | 58 | +-------------------------------------| 59 | | 0-x C string table entry | 60 | | (0-terminated) | 61 | +-------------------------------------| 62 | | 0-x C string table entry | 63 | | [...] | 64 | +-------------------------------------| 65 | |= DAEMS =============================| daemons table segment header 66 | | 0-4 C "DAEMS" | 67 | | 5 W number of daemon defs | 68 | +-------------------------------------| 69 | | 0 B x0 | 70 | | 1 B y0 | 71 | | 2 B x1 | 72 | | 3 B y1 | 73 | | 4 B opcode to trigger | 74 | +-------------------------------------| 75 | | 0-4 | 76 | | [...] | 77 | +-------------------------------------| 78 | |= OPCS ==============================| opcode table segment header 79 | | 0-3 C "OPCS" | 80 | | 4-5 W number of opcodes | 81 | +-------------------------------------+ 82 | | 0-7 opcode #0 | 83 | +-------------------------------------+ 84 | | 0-7 opcode #1 | 85 | +-------------------------------------+ 86 | | [...] | 87 | +-------------------------------------+ 88 | 89 | 90 | outdoor map tiles arrangement: 91 | 92 | 00 01 02 03 04 05 06 93 | 10 11 12 13 14 15 16 94 | 20 21 22 23 24 25 26 95 | 30 31 32 33 34 35 36 96 | 40 41 42 43 44 45 46 97 | 50 51 52 53 54 55 56 98 | 60 61 62 63 64 65 66 99 | 100 | 101 | 102 | artwork: 103 | 104 | foxhome 105 | https://www.flickr.com/photos/ergsart/22334549806/ 106 | Polenov russian village 107 | 108 | king's winter 109 | https://www.flickr.com/photos/ergsart/22158793548/ 110 | Levitan small village 111 | 112 | title dragon 113 | https://commons.wikimedia.org/wiki/File:2010-01-C%26E_Dragon.png 114 | 115 | inn1 116 | https://www.flickr.com/photos/ergsart/22168848738/ 117 | ostadeadriaen_tavern_scene_c_1665 118 | 119 | guild1 120 | https://commons.wikimedia.org/wiki/File:The_Merchant_Adventurers_Hall_The_Undercroft.jpg 121 | cc0 122 | -------------------------------------------------------------------------------- /c64.cfg: -------------------------------------------------------------------------------- 1 | FEATURES { 2 | STARTADDRESS: default = $0801; 3 | } 4 | SYMBOLS { 5 | __LOADADDR__: type = import; 6 | __EXEHDR__: type = import; 7 | __OVERLAYADDR__: type = import; 8 | __STACKSIZE__: type = weak, value = $0400; # 1k stack 9 | __OVERLAYSIZE__: type = weak, value = $4000; # 16k overlay 10 | __HIMEM__: type = weak, value = $D000; 11 | __OVERLAYSTART__: type = export, value = __HIMEM__ - __OVERLAYSIZE__; 12 | } 13 | MEMORY { 14 | ZP: file = "", define = yes, start = $0002, size = $001A; 15 | LOADADDR: file = "bin/drmain", start = %S - 2, size = $0002; 16 | HEADER: file = "bin/drmain", define = yes, start = %S, size = $000D; 17 | MAIN: file = "bin/drmain", define = yes, start = __HEADER_LAST__, size = __OVERLAYSTART__ - __HEADER_LAST__; 18 | BSS: file = "", start = __ONCE_RUN__, size = __OVERLAYSTART__ - __STACKSIZE__ - __ONCE_RUN__; 19 | OVL1ADDR: file = "bin/dungeon", start = __OVERLAYSTART__ - 2, size = $0002; 20 | OVL1: file = "bin/dungeon", start = __OVERLAYSTART__, size = __OVERLAYSIZE__; 21 | OVL2ADDR: file = "bin/city", start = __OVERLAYSTART__ - 2, size = $0002; 22 | OVL2: file = "bin/city", start = __OVERLAYSTART__, size = __OVERLAYSIZE__; 23 | OVL3ADDR: file = "bin/encounter", start = __OVERLAYSTART__ - 2, size = $0002; 24 | OVL3: file = "bin/encounter", start = __OVERLAYSTART__, size = __OVERLAYSIZE__; 25 | OVL4ADDR: file = "", start = __OVERLAYSTART__ - 2, size = $0002; 26 | OVL4: file = "", start = __OVERLAYSTART__, size = __OVERLAYSIZE__; 27 | OVL5ADDR: file = "", start = __OVERLAYSTART__ - 2, size = $0002; 28 | OVL5: file = "", start = __OVERLAYSTART__, size = __OVERLAYSIZE__; 29 | OVL6ADDR: file = "", start = __OVERLAYSTART__ - 2, size = $0002; 30 | OVL6: file = "", start = __OVERLAYSTART__, size = __OVERLAYSIZE__; 31 | OVL7ADDR: file = "", start = __OVERLAYSTART__ - 2, size = $0002; 32 | OVL7: file = "", start = __OVERLAYSTART__, size = __OVERLAYSIZE__; 33 | OVL8ADDR: file = "", start = __OVERLAYSTART__ - 2, size = $0002; 34 | OVL8: file = "", start = __OVERLAYSTART__, size = __OVERLAYSIZE__; 35 | OVL9ADDR: file = "", start = __OVERLAYSTART__ - 2, size = $0002; 36 | OVL9: file = "", start = __OVERLAYSTART__, size = __OVERLAYSIZE__; 37 | } 38 | SEGMENTS { 39 | ZEROPAGE: load = ZP, type = zp; 40 | LOADADDR: load = LOADADDR, type = ro; 41 | EXEHDR: load = HEADER, type = ro; 42 | STARTUP: load = MAIN, type = ro; 43 | LOWCODE: load = MAIN, type = ro, optional = yes; 44 | CODE: load = MAIN, type = ro; 45 | RODATA: load = MAIN, type = ro; 46 | DATA: load = MAIN, type = rw; 47 | INIT: load = MAIN, type = rw; 48 | ONCE: load = MAIN, type = ro, define = yes; 49 | BSS: load = BSS, type = bss, define = yes; 50 | OVL1ADDR: load = OVL1ADDR, type = ro; 51 | OVERLAY1: load = OVL1, type = ro, define = yes, optional = yes; 52 | OVL2ADDR: load = OVL2ADDR, type = ro; 53 | OVERLAY2: load = OVL2, type = ro, define = yes, optional = yes; 54 | OVL3ADDR: load = OVL3ADDR, type = ro; 55 | OVERLAY3: load = OVL3, type = ro, define = yes, optional = yes; 56 | OVL4ADDR: load = OVL4ADDR, type = ro; 57 | OVERLAY4: load = OVL4, type = ro, define = yes, optional = yes; 58 | OVL5ADDR: load = OVL5ADDR, type = ro; 59 | OVERLAY5: load = OVL5, type = ro, define = yes, optional = yes; 60 | OVL6ADDR: load = OVL6ADDR, type = ro; 61 | OVERLAY6: load = OVL6, type = ro, define = yes, optional = yes; 62 | OVL7ADDR: load = OVL7ADDR, type = ro; 63 | OVERLAY7: load = OVL7, type = ro, define = yes, optional = yes; 64 | OVL8ADDR: load = OVL8ADDR, type = ro; 65 | OVERLAY8: load = OVL8, type = ro, define = yes, optional = yes; 66 | OVL9ADDR: load = OVL9ADDR, type = ro; 67 | OVERLAY9: load = OVL9, type = ro, define = yes, optional = yes; 68 | } 69 | FEATURES { 70 | CONDES: type = constructor, 71 | label = __CONSTRUCTOR_TABLE__, 72 | count = __CONSTRUCTOR_COUNT__, 73 | segment = ONCE; 74 | CONDES: type = destructor, 75 | label = __DESTRUCTOR_TABLE__, 76 | count = __DESTRUCTOR_COUNT__, 77 | segment = RODATA; 78 | CONDES: type = interruptor, 79 | label = __INTERRUPTOR_TABLE__, 80 | count = __INTERRUPTOR_COUNT__, 81 | segment = RODATA, 82 | import = __CALLIRQ__; 83 | } 84 | -------------------------------------------------------------------------------- /src/c64.cfg: -------------------------------------------------------------------------------- 1 | FEATURES { 2 | STARTADDRESS: default = $0801; 3 | } 4 | SYMBOLS { 5 | __LOADADDR__: type = import; 6 | __EXEHDR__: type = import; 7 | __OVERLAYADDR__: type = import; 8 | __STACKSIZE__: type = weak, value = $0400; # 1k stack 9 | __OVERLAYSIZE__: type = weak, value = $4000; # 16k overlay 10 | __HIMEM__: type = weak, value = $D000; 11 | __OVERLAYSTART__: type = export, value = __HIMEM__ - __OVERLAYSIZE__; 12 | } 13 | MEMORY { 14 | ZP: file = "", define = yes, start = $0002, size = $001A; 15 | LOADADDR: file = "bin/drmain", start = %S - 2, size = $0002; 16 | HEADER: file = "bin/drmain", define = yes, start = %S, size = $000D; 17 | MAIN: file = "bin/drmain", define = yes, start = __HEADER_LAST__, size = __OVERLAYSTART__ - __HEADER_LAST__; 18 | BSS: file = "", start = __ONCE_RUN__, size = __OVERLAYSTART__ - __STACKSIZE__ - __ONCE_RUN__; 19 | OVL1ADDR: file = "bin/dungeon", start = __OVERLAYSTART__ - 2, size = $0002; 20 | OVL1: file = "bin/dungeon", start = __OVERLAYSTART__, size = __OVERLAYSIZE__; 21 | OVL2ADDR: file = "bin/city", start = __OVERLAYSTART__ - 2, size = $0002; 22 | OVL2: file = "bin/city", start = __OVERLAYSTART__, size = __OVERLAYSIZE__; 23 | OVL3ADDR: file = "bin/encounter", start = __OVERLAYSTART__ - 2, size = $0002; 24 | OVL3: file = "bin/encounter", start = __OVERLAYSTART__, size = __OVERLAYSIZE__; 25 | OVL4ADDR: file = "", start = __OVERLAYSTART__ - 2, size = $0002; 26 | OVL4: file = "", start = __OVERLAYSTART__, size = __OVERLAYSIZE__; 27 | OVL5ADDR: file = "", start = __OVERLAYSTART__ - 2, size = $0002; 28 | OVL5: file = "", start = __OVERLAYSTART__, size = __OVERLAYSIZE__; 29 | OVL6ADDR: file = "", start = __OVERLAYSTART__ - 2, size = $0002; 30 | OVL6: file = "", start = __OVERLAYSTART__, size = __OVERLAYSIZE__; 31 | OVL7ADDR: file = "", start = __OVERLAYSTART__ - 2, size = $0002; 32 | OVL7: file = "", start = __OVERLAYSTART__, size = __OVERLAYSIZE__; 33 | OVL8ADDR: file = "", start = __OVERLAYSTART__ - 2, size = $0002; 34 | OVL8: file = "", start = __OVERLAYSTART__, size = __OVERLAYSIZE__; 35 | OVL9ADDR: file = "", start = __OVERLAYSTART__ - 2, size = $0002; 36 | OVL9: file = "", start = __OVERLAYSTART__, size = __OVERLAYSIZE__; 37 | } 38 | SEGMENTS { 39 | ZEROPAGE: load = ZP, type = zp; 40 | LOADADDR: load = LOADADDR, type = ro; 41 | EXEHDR: load = HEADER, type = ro; 42 | STARTUP: load = MAIN, type = ro; 43 | LOWCODE: load = MAIN, type = ro, optional = yes; 44 | CODE: load = MAIN, type = ro; 45 | RODATA: load = MAIN, type = ro; 46 | DATA: load = MAIN, type = rw; 47 | INIT: load = MAIN, type = rw; 48 | ONCE: load = MAIN, type = ro, define = yes; 49 | BSS: load = BSS, type = bss, define = yes; 50 | OVL1ADDR: load = OVL1ADDR, type = ro; 51 | OVERLAY1: load = OVL1, type = ro, define = yes, optional = yes; 52 | OVL2ADDR: load = OVL2ADDR, type = ro; 53 | OVERLAY2: load = OVL2, type = ro, define = yes, optional = yes; 54 | OVL3ADDR: load = OVL3ADDR, type = ro; 55 | OVERLAY3: load = OVL3, type = ro, define = yes, optional = yes; 56 | OVL4ADDR: load = OVL4ADDR, type = ro; 57 | OVERLAY4: load = OVL4, type = ro, define = yes, optional = yes; 58 | OVL5ADDR: load = OVL5ADDR, type = ro; 59 | OVERLAY5: load = OVL5, type = ro, define = yes, optional = yes; 60 | OVL6ADDR: load = OVL6ADDR, type = ro; 61 | OVERLAY6: load = OVL6, type = ro, define = yes, optional = yes; 62 | OVL7ADDR: load = OVL7ADDR, type = ro; 63 | OVERLAY7: load = OVL7, type = ro, define = yes, optional = yes; 64 | OVL8ADDR: load = OVL8ADDR, type = ro; 65 | OVERLAY8: load = OVL8, type = ro, define = yes, optional = yes; 66 | OVL9ADDR: load = OVL9ADDR, type = ro; 67 | OVERLAY9: load = OVL9, type = ro, define = yes, optional = yes; 68 | } 69 | FEATURES { 70 | CONDES: type = constructor, 71 | label = __CONSTRUCTOR_TABLE__, 72 | count = __CONSTRUCTOR_COUNT__, 73 | segment = ONCE; 74 | CONDES: type = destructor, 75 | label = __DESTRUCTOR_TABLE__, 76 | count = __DESTRUCTOR_COUNT__, 77 | segment = RODATA; 78 | CONDES: type = interruptor, 79 | label = __INTERRUPTOR_TABLE__, 80 | count = __INTERRUPTOR_COUNT__, 81 | segment = RODATA, 82 | import = __CALLIRQ__; 83 | } 84 | -------------------------------------------------------------------------------- /.vscode/.ropeproject/config.py: -------------------------------------------------------------------------------- 1 | # The default ``config.py`` 2 | # flake8: noqa 3 | 4 | 5 | def set_prefs(prefs): 6 | """This function is called before opening the project""" 7 | 8 | # Specify which files and folders to ignore in the project. 9 | # Changes to ignored resources are not added to the history and 10 | # VCSs. Also they are not returned in `Project.get_files()`. 11 | # Note that ``?`` and ``*`` match all characters but slashes. 12 | # '*.pyc': matches 'test.pyc' and 'pkg/test.pyc' 13 | # 'mod*.pyc': matches 'test/mod1.pyc' but not 'mod/1.pyc' 14 | # '.svn': matches 'pkg/.svn' and all of its children 15 | # 'build/*.o': matches 'build/lib.o' but not 'build/sub/lib.o' 16 | # 'build//*.o': matches 'build/lib.o' and 'build/sub/lib.o' 17 | prefs['ignored_resources'] = ['*.pyc', '*~', '.ropeproject', 18 | '.hg', '.svn', '_svn', '.git', '.tox'] 19 | 20 | # Specifies which files should be considered python files. It is 21 | # useful when you have scripts inside your project. Only files 22 | # ending with ``.py`` are considered to be python files by 23 | # default. 24 | # prefs['python_files'] = ['*.py'] 25 | 26 | # Custom source folders: By default rope searches the project 27 | # for finding source folders (folders that should be searched 28 | # for finding modules). You can add paths to that list. Note 29 | # that rope guesses project source folders correctly most of the 30 | # time; use this if you have any problems. 31 | # The folders should be relative to project root and use '/' for 32 | # separating folders regardless of the platform rope is running on. 33 | # 'src/my_source_folder' for instance. 34 | # prefs.add('source_folders', 'src') 35 | 36 | # You can extend python path for looking up modules 37 | # prefs.add('python_path', '~/python/') 38 | 39 | # Should rope save object information or not. 40 | prefs['save_objectdb'] = True 41 | prefs['compress_objectdb'] = False 42 | 43 | # If `True`, rope analyzes each module when it is being saved. 44 | prefs['automatic_soa'] = True 45 | # The depth of calls to follow in static object analysis 46 | prefs['soa_followed_calls'] = 0 47 | 48 | # If `False` when running modules or unit tests "dynamic object 49 | # analysis" is turned off. This makes them much faster. 50 | prefs['perform_doa'] = True 51 | 52 | # Rope can check the validity of its object DB when running. 53 | prefs['validate_objectdb'] = True 54 | 55 | # How many undos to hold? 56 | prefs['max_history_items'] = 32 57 | 58 | # Shows whether to save history across sessions. 59 | prefs['save_history'] = True 60 | prefs['compress_history'] = False 61 | 62 | # Set the number spaces used for indenting. According to 63 | # :PEP:`8`, it is best to use 4 spaces. Since most of rope's 64 | # unit-tests use 4 spaces it is more reliable, too. 65 | prefs['indent_size'] = 4 66 | 67 | # Builtin and c-extension modules that are allowed to be imported 68 | # and inspected by rope. 69 | prefs['extension_modules'] = [] 70 | 71 | # Add all standard c-extensions to extension_modules list. 72 | prefs['import_dynload_stdmods'] = True 73 | 74 | # If `True` modules with syntax errors are considered to be empty. 75 | # The default value is `False`; When `False` syntax errors raise 76 | # `rope.base.exceptions.ModuleSyntaxError` exception. 77 | prefs['ignore_syntax_errors'] = False 78 | 79 | # If `True`, rope ignores unresolvable imports. Otherwise, they 80 | # appear in the importing namespace. 81 | prefs['ignore_bad_imports'] = False 82 | 83 | # If `True`, rope will insert new module imports as 84 | # `from import ` by default. 85 | prefs['prefer_module_from_imports'] = False 86 | 87 | # If `True`, rope will transform a comma list of imports into 88 | # multiple separate import statements when organizing 89 | # imports. 90 | prefs['split_imports'] = False 91 | 92 | # If `True`, rope will remove all top-level import statements and 93 | # reinsert them at the top of the module when making changes. 94 | prefs['pull_imports_to_top'] = True 95 | 96 | # If `True`, rope will sort imports alphabetically by module name instead 97 | # of alphabetically by import statement, with from imports after normal 98 | # imports. 99 | prefs['sort_imports_alphabetically'] = False 100 | 101 | # Location of implementation of 102 | # rope.base.oi.type_hinting.interfaces.ITypeHintingFactory In general 103 | # case, you don't have to change this value, unless you're an rope expert. 104 | # Change this value to inject you own implementations of interfaces 105 | # listed in module rope.base.oi.type_hinting.providers.interfaces 106 | # For example, you can add you own providers for Django Models, or disable 107 | # the search type-hinting in a class hierarchy, etc. 108 | prefs['type_hinting_factory'] = ( 109 | 'rope.base.oi.type_hinting.factory.default_type_hinting_factory') 110 | 111 | 112 | def project_opened(project): 113 | """This function is called after opening the project""" 114 | # Do whatever you like here! 115 | -------------------------------------------------------------------------------- /src/main.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2019 Stephan Kleinert 3 | * 4 | * This program is free software; you can redistribute it and/or 5 | * modify it under the terms of the GNU Lesser General Public 6 | * License as published by the Free Software Foundation; either 7 | * version 2.1 of the License, or (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | * Lesser General Public License for more details. 13 | * 14 | * You should have recxeived a copy of the GNU Lesser General Public 15 | * License along with main.c; if not, write to the Free Software 16 | * Foundation, Inc., 51 Franklin Street, Fifth Floor Boston, MA 02110-1301, USA 17 | */ 18 | 19 | #include <6502.h> 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | 26 | #include 27 | #include 28 | #include 29 | 30 | #include "globals.h" 31 | 32 | #include "congui.h" 33 | #include "debug.h" 34 | #include "dungeon.h" 35 | #include "guild.h" 36 | 37 | #include "encounter.h" 38 | #include "monster.h" 39 | #include "spell.h" 40 | 41 | #include "dispatcher.h" 42 | #include "memory.h" 43 | 44 | #include "city.h" 45 | #include "menu.h" 46 | #include "utils.h" 47 | 48 | #ifndef DRE_VERSION 49 | #define DRE_VERSION "0.1a" 50 | #endif 51 | 52 | #ifndef DRE_DATE 53 | #define DRE_DATE "12/29/2019" 54 | #endif 55 | 56 | #ifndef DRE_BUILDNUM 57 | #define DRE_BUILDNUM "-666" 58 | #endif 59 | 60 | char *drbuf; 61 | 62 | himemPtr itemBase; 63 | himemPtr monstersBase; 64 | himemPtr citiesBase; 65 | 66 | byte hasLoadedGame; 67 | byte devmode; 68 | 69 | void initEngine(void); 70 | void runCityMenu(void); 71 | void doGuild(void); 72 | void loadSaved(void); 73 | 74 | const char *prompt= 75 | "DREngine/m65 v" DRE_VERSION " build " DRE_BUILDNUM "\n" DRE_DATE "\n\n"; 76 | 77 | void loadResources(void) { 78 | unsigned int readBytes; 79 | himemPtr configTop; 80 | 81 | itemBase= CFG_STORAGE_BASE; 82 | readBytes= loadExt("items", itemBase, false); 83 | 84 | monstersBase= itemBase + readBytes; 85 | readBytes= loadExt("monsters", monstersBase, false); 86 | 87 | citiesBase= monstersBase + readBytes; 88 | readBytes= loadExt("cities", citiesBase, false); 89 | 90 | configTop= citiesBase + readBytes; 91 | if (configTop>=SEENMAP_BASE) { 92 | cg_fatal("config too large: %lx",configTop); 93 | } 94 | } 95 | 96 | void initEngine(void) { 97 | mega65_io_enable(); 98 | drbuf= (char *)malloc(DRBUFSIZE); 99 | srand((unsigned int)DRE_BUILDNUM); 100 | puts("\n"); // cancel leftover quote mode from wrapper or whatever 101 | cbm_k_bsout(14); // lowercase 102 | cbm_k_bsout(147); // clr 103 | puts(prompt); 104 | lcopy(0x5f000, (long)drbuf, 4); 105 | if (drbuf[0] == 0x53 && drbuf[1] == 0x4b) { 106 | devmode= true; 107 | } else if (drbuf[0] != 0x23 || drbuf[1] != 0x45) { 108 | cg_fatal("Please BOOT the dragon rock disc to\n" 109 | "correctly initialize the game."); 110 | } 111 | 112 | lpoke(0x5f000, 0); 113 | lpoke(0x5f001, 0); 114 | 115 | loadModules(); 116 | // puts("init monster rows"); 117 | initMonsterRows(); 118 | loadResources(); 119 | // puts("init party"); 120 | hasLoadedGame= loadParty(); 121 | gLoadedDungeonIndex= 255; 122 | gCurrentGameMode= gm_init; 123 | cg_init(); 124 | 125 | // after graphics initialization, it's safe to free drbuf and using 126 | // the old text screen instead, thus saving a few bytes... 127 | 128 | free(drbuf); 129 | drbuf= (char *)0x400; 130 | 131 | } 132 | 133 | void debugEncounter(void) { 134 | gCurrentGameMode= gm_init; 135 | addNewMonster(1, 1, 3, 4, 0); 136 | addNewMonster(2, 1, 2, 5, 1); 137 | addNewMonster(0xa0, 1, 1, 1, 2); 138 | prepareForGameMode(gm_encounter); 139 | mainDispatchLoop(); 140 | } 141 | 142 | void debugDungeon(void) { 143 | gCurrentDungeonIndex= 0; 144 | gStartXPos= 15; 145 | gStartYPos= 1; 146 | // gCurrentDungeonIndex= 1; 147 | // gStartXPos= 10; 148 | // gStartYPos= 18; 149 | gCurrentGameMode= gm_init; 150 | prepareForGameMode(gm_dungeon); 151 | mainDispatchLoop(); 152 | } 153 | 154 | int main() { 155 | static char choice; 156 | byte i; 157 | char *test; 158 | char *mainMenu[]= {"Load saved game", NULL, NULL}; 159 | 160 | initEngine(); 161 | cg_borders(false); 162 | 163 | sprintf(drbuf, "Start in %s", getNameForCityID(0)); 164 | mainMenu[1]= drbuf; 165 | choice= runMenu(mainMenu, 4, 11, true, true); 166 | 167 | gCurrentCityIndex= 0; 168 | prepareForGameMode(gm_city); 169 | 170 | if (choice == 100) { 171 | debugDungeon(); 172 | } 173 | 174 | if (choice == 101) { 175 | debugEncounter(); 176 | } 177 | 178 | if (choice == 0 && hasLoadedGame) { 179 | // determine last city from saved party 180 | gCurrentCityIndex= party[0]->city; 181 | } else { 182 | // remove saved party if not loading saved game 183 | for (choice= 0; choice < PARTYSIZE; party[choice++]= 0) 184 | ; 185 | } 186 | 187 | mainDispatchLoop(); 188 | 189 | return 0; 190 | } 191 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # DragonRock 2 | 3 | A role playing game for the MEGA65. 4 | 5 | I got the idea for "Dragon Rock" upon realizing that there are hardly any native role playing games for the Commodore TED series of computers (c16, c116, plus/4). Since I always had soft spot for the underdogs and also always wanted to implement a classic CRPG, I decided to fill this gap. 6 | 7 | But once I had begun implementing DRs advanced features (such as a compiled dungeon description language and a bytecode interpreter along with it), I realized that the poor old TEDs would have a hard time processing all the stuff, and the project slowed down and finally was put on hold. 8 | 9 | Then along came the amazing MEGA65 re-imagination of Commodore's last 8-bit-machine, and I knew, the platform to complete "Dragon Rock" was finally there (actually, some 20 years ago, I was the owner of one of the fabled C65 prototypes, there are even still some demos by yours truly floating around on the 'net, so developing for the MEGA65 felt like coming home to me... but that's a different story). 10 | 11 | Gameplay-wise "Dragon Rock" is modelled after "Phantasie" (my favourite crpg series on 8-bit computers, because it's simple and challenging at the same time). It's still very much work in progress and contributions are of course welcome (oh well, who am I kidding ;-)). 12 | 13 | "Dragon Rock" features 14 | 15 | - scripted, dynamic dungeons (dungeons are laid out in a map editor and an accompanying DungeonScript. The build tools then convert the map and the script into bytecode which the actual game uses) 16 | 17 | - a map editor 18 | 19 | - full colour graphics (at the moment for cities, special encounters and buildings) 20 | 21 | - an easy to use menu system 22 | 23 | 24 | Here's some screenshots of what's in there by now: 25 | 26 |
Loading screen 27 | 28 |
29 | Home, sweet home 30 | 31 |
32 | Creating a character 33 | 34 |
35 | Outdoor map view 36 | 37 |
38 | A dungeon 39 | 40 |
41 | Visiting the guild 42 | 43 | 44 | ![Screen7](screenshots/mapEditor.png) 45 | 46 | Of course, "Dragon Rock" comes with its own map editor... 47 | 48 | ![Screen8](screenshots/mapCompiler.png) 49 | 50 | ...and map compiler, which translates a 'DungeonScript' file into bytecode, which then 51 | gets interpreted by the main binary on the MEGA65. 52 | 53 | ## Overview 54 | 55 | ## System Requirements 56 | 57 | - MEGA65 computer or compatible emulator 58 | - Storage device for game data (SD card recommended) 59 | 60 | 61 | ## Building From Source 62 | 63 | ### Requirements 64 | 65 | - CC65 Compiler Suite, version 2.19 or later 66 | - Python 3.6 or later 67 | - Git 68 | - c1541 from VICE (for building the disc image) 69 | - LaTeX (for building the game manual) 70 | 71 | ### Setup 72 | 73 | 1. clone the project: 74 | ``` 75 | git clone https://github.com/steph72/dragonrock-mega65.git 76 | ``` 77 | 78 | 2. install the project submodules: 79 | ``` 80 | git submodule update --init 81 | ``` 82 | 83 | 3. setup the python environment & requirements. Don't worry – there's a script for that ;-) 84 | ``` 85 | ./setupPythonEnvironment.sh 86 | ``` 87 | 88 | 4. activate the python environment 89 | ``` 90 | source dr_venv/bin/activate 91 | ``` 92 | 93 | That's it, you're ready to go! 94 | 95 | ### Building 96 | 97 | DragonRock uses the scons build system because it is so very much nicer and more flexible than Makefiles. Scons is installed alongside with the python environment. 98 | 99 | #### Compile the game only: 100 | ``` 101 | # Compile the game only 102 | scons 103 | # or 104 | scons compile 105 | 106 | # Compile and build the disc image 107 | scons build 108 | 109 | # Clean all build artifacts 110 | scons clean 111 | ``` 112 | 113 | 114 | ## Development Tools 115 | 116 | DragonRock comes with custom development tools to create and extend the game world. These are described in detail in the doc folder. 117 | 118 | ### tools/maped.py 119 | A map editor for dungeon and outoors maps 120 | 121 | ### tools/mc 122 | A map compiler. 123 | The game content is scripted in a simple language called DRScript. The map compiler converts DRScript into bytecode which can be used in the game. 124 | 125 | ### tools/png2dbm.py 126 | A PNG to DBM converter for displaying ingame graphics as super extended colour mode images 127 | 128 | 129 | 130 | 131 | ### Build Commands 132 | 133 | 134 | 135 | ### Directory Structure 136 | 137 | - `src/` - Source code files 138 | - `tools/` - Development and build tools 139 | - `maps/` - Game maps and level data 140 | - `graphics/` - Graphic resources 141 | - `gamedata-src/` - Source files for game data 142 | 143 | When building Dragon Rock, the artifacts are placed in the following folders: 144 | - `obj/` - Compiled object files (created during build) 145 | - `bin/` - Binary output files (created during build) 146 | - `gamedata/` - Game resources and data files (created during build) 147 | - `disc/` - Disc image files (created during build) 148 | 149 | ## Contributing 150 | 151 | Contributions to DragonRock are welcome! Whether it's bug fixes, new features, or content additions, feel free to fork the repository and submit a pull request. 152 | 153 | 154 | ## Acknowledgments 155 | 156 | - Thanks to the MEGA65 team for creating an amazing platform 157 | -------------------------------------------------------------------------------- /maps/outdoor33.drs: -------------------------------------------------------------------------------- 1 | 2 | includemap "maps/outdoor33.drm" 3 | 4 | $ msgWaitkey,"\n-- key -- " 5 | 6 | 7 | ; ----------- main road -------------------------- 8 | 9 | connectLabel mainRoad2: 13,2 - 24,2 10 | connectLabel mainRoad3: 24,3 - 26,3 11 | connectLabel mainRoad4: 26,4 - 31,4 12 | 13 | $ msgMainRoad,""" 14 | A wide and comfortable road. 15 | """ 16 | 17 | mainRoad1: 18 | mainRoad2: 19 | mainRoad3: 20 | mainRoad4: 21 | NSTAT_O msgMainRoad 22 | --- 23 | 24 | ; ------------------------------------- 25 | 26 | connectLabel fhPath1: 10,14 - 14,14 27 | connectLabel fhPath2: 14,15 - 20,15 28 | connectLabel terrorShack: 10,2 - 11,2 29 | connectLabel terrorShackEntry: 10,1 - 11,1 30 | connectLabel dirtRoad: 10,4 31 | connectLabel mountainPath: 10,3 32 | 33 | $ msgFhPath,"A narrow driveway" 34 | 35 | $ msgTerrorShack,""" 36 | A set of wooden doors has been built 37 | into the mountain, apparently not long 38 | ago. A crude sign on the door says: 39 | 'THE NOYSY PIG RIDERZ. KEEP OUT!' 40 | """ 41 | 42 | $ msgDirtRoad,""" 43 | You notice an unusual amount of rubble 44 | and junk piling up at the north side 45 | of the road. 46 | """ 47 | 48 | $ msgMountainPath,""" 49 | A small path leads to the mountains 50 | in the north. 51 | """ 52 | 53 | fhPath1: 54 | fhPath2: 55 | NSTAT_O msgFhPath 56 | --- 57 | 58 | dirtRoad: 59 | NSTAT_O msgDirtRoad 60 | --- 61 | 62 | mountainPath: 63 | NSTAT_O msgMountainPath 64 | --- 65 | 66 | terrorShack: 67 | NSTAT_O msgTerrorShack 68 | --- 69 | 70 | $ msgTerrorShackEntry,"Enter new location (y/n)?" 71 | 72 | terrorShackEntry: 73 | DISP_S msgTerrorShackEntry,True 74 | YESNO_B enterShack 75 | REDRAW 76 | --- 77 | 78 | enterShack: 79 | ENTER_D 1,10,18 ;load dungeonMap 1 80 | --- 81 | 82 | ; ------------------------------------- 83 | ; -------- troll bridge --------------- 84 | ; ------------------------------------- 85 | 86 | connectLabel lTrollBridge: 16,15 87 | 88 | defDaemon bikerGangDaemon: 9,2 - 15,16 89 | 90 | $ msgBridgeInitial1,""" 91 | As you arrive at the bridge, you meet 92 | a group of adolescent trolls. Some of 93 | them are hanging around and kicking 94 | the dirt, laughing stupidly. Others 95 | are still mounted on their riding 96 | pigs, occasionally giving their 97 | mounts' ears a violent twist, causing 98 | them to squeal in pain. 99 | 100 | """ 101 | 102 | $ msgBridgeInitial2,""" 103 | One of the trolls stands apart from 104 | the others. He's wearing a black 105 | leather armor and he's sitting atop 106 | a particularly ugly pig. He rides 107 | up to you and proclaims: 108 | 109 | 'ME'S LUKASZ, DIZ MY BRIDGE, DIZ MY 110 | BOYZ! WE'S RIDING DA NOISY PIGS! 111 | WE'S THE GREATEST! YOU WANNA PASS, 112 | YOU GONNA PAY. 50 GOLD!' 113 | 114 | """ 115 | 116 | $ msgBridge,""" 117 | It's Lukasz' boys again. Lukasz can 118 | be seen somewhere in the background, 119 | zoooming around on his riding pig, 120 | making it squeal from time to time. 121 | 122 | His cronies step up to face you. 123 | 124 | """ 125 | 126 | $ msgPayQuestion,""" 127 | Do you pay Lukasz' toll?""" 128 | 129 | $ msgPay,""" 130 | 131 | They laugh stupidly, then they 132 | let you go. As you pass them, they 133 | make their pigs squeal loudly. 134 | 135 | """ 136 | 137 | $ msgLukasUrlaub,""" 138 | The bridge feels strangely quiet and 139 | peaceful. 140 | """ 141 | 142 | $ msgLukasCoward,""" 143 | 144 | As the other trolls charge towards 145 | you, Lukasz spurs his ugly riding 146 | pig, drops back and vanishes 147 | from view. 148 | 149 | """ 150 | 151 | $ msgLukasCoward2,""" 152 | 153 | They charge and attack you. Again, 154 | Lukasz himself vanishes from view. 155 | 156 | """ 157 | 158 | bikerGangDaemon: 159 | RANDOM_B 100, lBikerGangAttack ; 10% chance 160 | --- 161 | 162 | $ msgBikersAttack, "Pig riding trolls attack!" 163 | lBikerGangAttack: 164 | DISP_S msgBikersAttack 165 | WKEY msgWaitkey 166 | REDRAW 167 | --- 168 | 169 | lTrollBridge: 170 | DISP msgBridgeInitial1,True 171 | WKEY msgWaitkey 172 | DISP msgBridgeInitial2,True 173 | WKEY msgWaitkey 174 | ALTER lTrollBridge, lTrollBridgeLater, 18 175 | DISP msgPayQuestion,True 176 | YESNO_B payday 177 | DISP msgLukasCoward 178 | WKEY msgWaitkey 179 | CLRENC 180 | ADDENC 2,1,2,3,1 181 | GOENC wWin,wLose 182 | wWin: 183 | wLose: 184 | REDRAW 185 | --- 186 | 187 | lTrollBridgeLater: 188 | RANDOM_B 400,lLukasUrlaub ; 40% chance that the boys are not there 189 | DISP msgBridge,True 190 | DISP msgPayQuestion 191 | YESNO_B payday 192 | DISP msgLukasCoward2 193 | WKEY msgWaitkey 194 | REDRAW 195 | --- 196 | 197 | lLukasUrlaub: 198 | NSTAT_O msgLukasUrlaub 199 | --- 200 | 201 | payday: 202 | DISP msgPay 203 | WKEY msgWaitkey 204 | REDRAW 205 | --- 206 | 207 | ; ---------- foxhome ------------ 208 | 209 | connectLabel city_foxhome: 22,15 - 23,15 210 | connectLabel city_road: 21,15 211 | connectLabel foxhome_signpost: 9,14 212 | 213 | $ msgCity,""" 214 | The village of Foxhome. Do you want 215 | to enter? (yes/no):""" 216 | 217 | $ msgCityRoad,""" 218 | To the east lies the quiet little 219 | village of Foxhome. 220 | """ 221 | 222 | $ msgFoxhomeSignpost,""" 223 | A signpost here says: 224 | 'Foxhome - 60 miles east' 225 | """ 226 | 227 | foxhome_signpost: 228 | NSTAT_O msgFoxhomeSignpost 229 | --- 230 | 231 | city_road: 232 | NSTAT_O msgCityRoad 233 | --- 234 | 235 | city_foxhome: 236 | DISP msgCity,True 237 | YESNO enterCity 238 | REDRAW 239 | --- 240 | 241 | enterCity: 242 | ENTER_C 0 243 | --- -------------------------------------------------------------------------------- /src/guild.c: -------------------------------------------------------------------------------- 1 | 2 | #include 3 | //#include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #include "globals.h" 10 | #include "character.h" 11 | #include "congui.h" 12 | #include "guild.h" 13 | 14 | character *guild; 15 | 16 | FILE *outfile; 17 | 18 | void newGuildMember(byte city); 19 | void _listGuildMembers(void); 20 | void listGuildMembers(void); 21 | 22 | // clang-format off 23 | #pragma code-name(push, "OVERLAY2"); 24 | #pragma rodata-name (push, "OVERLAY2") 25 | #pragma local-strings (push,on) 26 | // clang-format on 27 | 28 | void _listGuildMembers(void) { 29 | static byte i, x, y; 30 | static byte charsPerRow= GUILDSIZE / 2; 31 | character *tempChar; 32 | 33 | for (i= 0; i < GUILDSIZE; ++i) { 34 | if (guild[i].status != deleted) { 35 | tempChar= &guild[i]; 36 | x= (20 * (i / charsPerRow)); 37 | y= (4 + (i % charsPerRow)); 38 | cg_gotoxy(x, y); 39 | if (isInParty(i)) { 40 | cg_putc('*'); 41 | } else { 42 | cg_putc(' '); 43 | } 44 | cg_printf("%2d %.10s", i + 1, tempChar->name); 45 | cg_gotoxy(x + 14, y); 46 | cg_printf("%s-%d", gClassesS[tempChar->aClass], tempChar->city + 1); 47 | } 48 | } 49 | } 50 | 51 | void listGuildMembers(void) { 52 | cg_titlec(COLOR_GREEN, 0, 53 | "Guild Members"); 54 | _listGuildMembers(); 55 | cg_putsxy(0, 23, "-- key --"); 56 | cg_getkey(); 57 | } 58 | 59 | void cleanupParty(void) { 60 | byte i; 61 | for (i= 0; i < PARTYSIZE - 1; ++i) { 62 | if (party[i] == NULL) { 63 | if (party[i + 1] != NULL) { 64 | party[i]= party[i + 1]; 65 | party[i + 1]= NULL; 66 | } 67 | } 68 | } 69 | } 70 | 71 | void dropFromParty(void) { 72 | static byte pm; 73 | 74 | cg_clearxy(0, 26, 40); 75 | cg_putsxy(2, 26, "Remove whom (0=cancel)"); 76 | pm = cg_getnum(2); 77 | if (pm == 0) 78 | return; 79 | --pm; 80 | if (pm >= PARTYSIZE) { 81 | cg_displayErrorStatus("You wish!"); 82 | return; 83 | } 84 | free(party[pm]); 85 | party[pm]= NULL; 86 | cleanupParty(); 87 | } 88 | 89 | byte isInParty(byte guildIdx) { 90 | static byte i; 91 | for (i= 0; i < PARTYSIZE; i++) { 92 | if (party[i] && party[i]->guildSlot == guildIdx) { 93 | return true; 94 | } 95 | } 96 | return false; 97 | } 98 | 99 | void addToParty(void) { 100 | static signed char slot; 101 | unsigned char gmIndex; 102 | 103 | character *newPartyCharacter; 104 | 105 | cg_clearxy(0, 26, 40); 106 | slot= nextFreePartySlot(); 107 | if (slot == -1) { 108 | cg_displayErrorStatus("no room in party"); 109 | return; 110 | } 111 | cg_clrscr(); 112 | cg_titlec(COLOR_YELLOW, 0, "Add guild member"); 113 | 114 | _listGuildMembers(); 115 | cg_putsxy(0, 26, "Add which guild member (0=cancel)?"); 116 | gmIndex = cg_getnum(2); 117 | if (gmIndex == 0) { 118 | return; 119 | } 120 | --gmIndex; 121 | if (gmIndex >= GUILDSIZE) { 122 | cg_displayErrorStatus("What is it with you?!"); 123 | return; 124 | } 125 | if (guild[gmIndex].status == deleted) { 126 | cg_displayErrorStatus("nobody there"); 127 | return; 128 | } 129 | if (isInParty(gmIndex)) { 130 | cg_displayErrorStatus("already in party"); 131 | return; 132 | } 133 | 134 | newPartyCharacter= malloc(sizeof(character)); 135 | memcpy(newPartyCharacter, (void *)&guild[gmIndex], sizeof(character)); 136 | party[slot]= newPartyCharacter; 137 | } 138 | 139 | void purgeGuildMember(void) { 140 | static byte idx; 141 | cg_titlec(COLOR_RED, 0, "Purge guild member"); 142 | cg_textcolor(COLOR_RED); 143 | _listGuildMembers(); 144 | cg_putsxy(0, 26, "Purge which member (0=cancel)? "); 145 | idx = cg_getnum(2); 146 | if (idx == 0) { 147 | return; 148 | } 149 | idx--; 150 | if (idx >= GUILDSIZE) { 151 | cg_displayErrorStatus("Are you working in QA?"); 152 | return; 153 | } 154 | if (isInParty(idx)) { 155 | cg_displayErrorStatus("Member is currently in the party!"); 156 | return; 157 | } 158 | guild[idx].status= deleted; 159 | } 160 | 161 | signed char nextFreePartySlot(void) { 162 | signed char idx= -1; 163 | while (++idx < PARTYSIZE) { 164 | if (party[idx] == NULL) { 165 | return idx; 166 | } 167 | } 168 | return -1; 169 | } 170 | 171 | signed char nextFreeGuildSlot(void) { 172 | signed char idx= -1; 173 | while (++idx < GUILDSIZE) { 174 | if (guild[idx].status == deleted) { 175 | return idx; 176 | } 177 | } 178 | return -1; 179 | } 180 | 181 | void saveGuild(void) { 182 | outfile= fopen("gdata", "w"); 183 | fwrite(guild, GUILDSIZE * sizeof(character), 1, outfile); 184 | fclose(outfile); 185 | } 186 | 187 | void saveParty(void) { 188 | static byte i, c; 189 | outfile= fopen("pdata", "w"); 190 | c= partyMemberCount(); 191 | fputc(c, outfile); 192 | for (i= 0; i < c; ++i) { 193 | fwrite(party[i], sizeof(character), 1, outfile); 194 | } 195 | fclose(outfile); 196 | } 197 | 198 | byte loadGuild(void) { 199 | FILE *infile; 200 | 201 | infile= fopen("gdata", "r"); 202 | if (!infile) { 203 | return false; 204 | } 205 | fread(guild, GUILDSIZE * sizeof(character), 1, infile); 206 | fclose(infile); 207 | 208 | return true; 209 | } 210 | 211 | byte initGuild() { 212 | initGuildMem(); 213 | return loadGuild(); 214 | } 215 | 216 | void initGuildMem(void) { 217 | static unsigned int sizeBytes= 0; 218 | sizeBytes= GUILDSIZE * sizeof(character); 219 | guild= (character *)malloc(sizeBytes); 220 | if (guild == NULL) { 221 | cg_fatal("no guild mem"); 222 | } 223 | bzero(guild, sizeBytes); 224 | } 225 | 226 | // clang-format off 227 | #pragma code-name(pop) 228 | #pragma rodata-name(pop) 229 | #pragma local-strings(pop) 230 | // clang-format on 231 | -------------------------------------------------------------------------------- /src/armory.c: -------------------------------------------------------------------------------- 1 | //#include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | #include "globals.h" 8 | #include "character.h" 9 | #include "congui.h" 10 | #include "city.h" 11 | 12 | #define SHOP_INV_SIZE 32 13 | #define ITEMS_PER_PAGE 12 14 | #define RESTOCK_FREQ 5 // restock every n visits 15 | 16 | byte *shopInventory; 17 | byte numCityVisits; 18 | 19 | // clang-format off 20 | #pragma code-name(push, "OVERLAY2"); // "CITY" segment 21 | #pragma rodata-name (push, "OVERLAY2") 22 | #pragma local-strings (push,on) 23 | // clang-format on 24 | 25 | 26 | byte addItemIDToShopInventory(byte itemID) { 27 | byte i; 28 | for (i= 0; i < SHOP_INV_SIZE; ++i) { 29 | if (!shopInventory[i]) { 30 | shopInventory[i]= itemID; 31 | return true; 32 | } 33 | } 34 | return false; 35 | } 36 | 37 | byte numberOfItemsWithID(byte itemID) { 38 | byte i; 39 | byte count= 0; 40 | for (i= 0; i < SHOP_INV_SIZE; ++i) { 41 | if (shopInventory[i] == itemID) { 42 | ++count; 43 | } 44 | } 45 | return count; 46 | } 47 | 48 | void restockItem(byte itemID, byte count) { 49 | byte currentCount; 50 | byte toAdd; 51 | currentCount= numberOfItemsWithID(itemID); 52 | if (currentCount >= count) { 53 | return; 54 | } 55 | toAdd= count - currentCount; 56 | while (toAdd > 0) { 57 | addItemIDToShopInventory(itemID); 58 | toAdd--; 59 | } 60 | } 61 | 62 | void restockShop(void) { 63 | restockItem(0x01, 3); 64 | restockItem(0x02, 3); 65 | restockItem(0x03, 3); 66 | } 67 | 68 | void releaseArmory(void) { free(shopInventory); } 69 | 70 | void initArmory() { 71 | FILE *shopInvFile; 72 | numCityVisits= 0; 73 | sprintf(drbuf, "s%d", gCurrentCityIndex); 74 | shopInventory= (byte *)malloc(SHOP_INV_SIZE); 75 | shopInvFile= fopen(drbuf, "rb"); 76 | if (shopInvFile) { 77 | numCityVisits= fgetc(shopInvFile); 78 | fread(shopInventory, SHOP_INV_SIZE, 1, shopInvFile); 79 | fclose(shopInvFile); 80 | } else { 81 | memset(shopInventory, 0, SHOP_INV_SIZE); 82 | } 83 | if (numCityVisits % RESTOCK_FREQ == 0) { 84 | restockShop(); 85 | } 86 | numCityVisits++; 87 | } 88 | 89 | void saveArmory() { 90 | FILE *shopInvFile; 91 | sprintf(drbuf, "s%d", gCurrentCityIndex); 92 | shopInvFile= fopen(drbuf, "wb"); 93 | fputc(numCityVisits, shopInvFile); 94 | fwrite(shopInventory, SHOP_INV_SIZE, 1, shopInvFile); 95 | fclose(shopInvFile); 96 | } 97 | 98 | void dispInvFromIndex(byte idx) { 99 | byte i; 100 | byte itemIdx; 101 | item *anItem; 102 | for (i= 0; i < ITEMS_PER_PAGE; ++i) { 103 | itemIdx= idx + i; 104 | if (itemIdx >= SHOP_INV_SIZE) { 105 | return; 106 | } 107 | cg_gotoxy(3, 3 + i); 108 | if (shopInventory[itemIdx]) { 109 | anItem= inventoryItemForID(shopInventory[itemIdx]); 110 | printf("%c %-10s %5u", 'A' + i, nameOfInventoryItem(anItem), anItem->price); 111 | } 112 | } 113 | } 114 | 115 | unsigned int salePrice(character *shopper, item *anItem) { 116 | unsigned long p; 117 | signed char charBonus; 118 | 119 | charBonus= bonusValueForAttribute(shopper->attributes[aCHR]); 120 | p= (anItem->price * (100UL + (10 * charBonus))) / 100UL; 121 | return p; 122 | } 123 | 124 | 125 | void sellItem(character *shopper) { 126 | byte val; 127 | byte slot; 128 | unsigned int price; 129 | item *anItem; 130 | byte sellQuit; 131 | sellQuit= false; 132 | do { 133 | cg_clearFromTo(3, 23); 134 | cg_gotoxy(1, 4); 135 | cg_puts("--- selling an item ---"); 136 | cg_gotoxy(0, 23); 137 | displayInventoryAtRow(shopper, 7, 'A'); 138 | cg_gotoxy(0, 20); 139 | cg_puts("Sell which item (x to abort) "); 140 | cg_cursor(1); 141 | slot= cg_getkey(); 142 | cg_cursor(0); 143 | slot-= 'a'; 144 | if (slot > INV_SIZE) { 145 | return; 146 | } 147 | if (shopper->inventory[slot] == 0) { 148 | return; 149 | } 150 | anItem= inventoryItemForID(shopper->inventory[slot]); 151 | price= anItem->price; 152 | cg_gotoxy(0, 20); 153 | cg_printf("\nSell %s for %u coins (y/n)?", nameOfInventoryItem(anItem), price); 154 | cg_cursor(1); 155 | do { 156 | val= cg_getkey(); 157 | } while (val != 'y' && val != 'n'); 158 | cg_putc(val); 159 | cg_cursor(0); 160 | if (val!='y') { 161 | return; 162 | } 163 | if (addItemIDToShopInventory(anItem->id)) { 164 | shopper->inventory[slot] = 0; 165 | cg_cursor(1); 166 | cg_puts("\r\nSell another (y/n)? "); 167 | val=cg_getkey(); 168 | sellQuit = (val=='n'); 169 | } else { 170 | cg_puts("\nshop is full!\n--key--"); 171 | cg_getkey(); 172 | return; 173 | } 174 | } while (!sellQuit); 175 | } 176 | 177 | void doArmory(void) { 178 | char cmd; 179 | character *shopper; 180 | cg_cursor(1); 181 | cg_putsxy(0, 21, "Who wants to go shopping? "); 182 | cmd= cg_getkey(); 183 | if (cmd < '1' || cmd > '6') { 184 | return; 185 | } 186 | cmd-= '1'; 187 | if (party[cmd] == NULL) { 188 | return; 189 | } 190 | 191 | shopper= party[cmd]; 192 | 193 | do { 194 | cg_cursor(0); 195 | sprintf(drbuf, "%s Armory", getNameForCityID(gCurrentCityIndex)); 196 | cg_titlec(COLOR_GREEN, 0, 197 | drbuf); 198 | cg_gotoxy(0, 19); 199 | cg_printf("%s coins: %d", shopper->name, shopper->gold); 200 | cg_puts("\n\nA)-L) buy item S)ell item eX)it shop"); 201 | dispInvFromIndex(0); 202 | cg_cursor(1); 203 | cmd= cg_getkeyP(0,22,">"); 204 | cg_cursor(0); 205 | if (cmd == 's') { 206 | sellItem(shopper); 207 | } 208 | } while (cmd != 'x'); 209 | } 210 | 211 | // clang-format off 212 | #pragma code-name(pop); // "CITY" segment 213 | #pragma rodata-name (pop) 214 | #pragma local-strings (pop) 215 | // clang-format on -------------------------------------------------------------------------------- /manual/manual.tex: -------------------------------------------------------------------------------- 1 | \documentclass[12pt]{scrbook} 2 | 3 | \usepackage{geometry} 4 | \usepackage{graphicx} 5 | 6 | \graphicspath{ {./images/} } 7 | 8 | \geometry{ 9 | a5paper 10 | } 11 | 12 | \begin{document} 13 | \title{Dragon Rock} 14 | \subtitle{A fantasy role playing game for the MEGA65} 15 | \author{7Turtles Software} 16 | \maketitle 17 | 18 | \includegraphics[width=\textwidth]{illustrations/ddungeon} 19 | 20 | 21 | \chapter*{The story so far\dots} 22 | 23 | For the longest time, everything seemed to be going just right in the Tianad province. People were happy - perhaps they even were the happiest people in all of the great kingdom of Narcordia. 24 | 25 | And why wouldn't they? 26 | 27 | The summers were warm and winters mild, harvest had been great and plentiful for years on end now, commerce was flourishing, the roads were reasonably secure, almost every citizen of Tianad had enough to eat and drink and a roof over their heads... it really seemed as if the gods were smiling down on the little province on the southeast border of Nacordia. 28 | 29 | The change came slowly. At first, no one really noticed how fewer and fewer merchants from foreign lands were to be seen in the five major cities of Tianad. But then, as food and drink got scarce, stories of people disappearing from the roads at night were told in hushed voices in Tianads ever less frequented inns. 30 | 31 | Then the raids started. Troops of all kinds of monsters and thieves started paying regular visits to many of the smaller villages of Tianad, stealing and pillaging from merchants and private homes alike. 32 | 33 | Even the weather seemed to change -- summers grew hotter and hotter, torrential rains in autumn washed away the dried up ground, and winters were bitter and deadly cold. 34 | 35 | For some time, people still hoped that the king would send reinforcements from his Dragon Guard to get the situation under control -- but reinforcements never came, and the few and in between remaining dragon guard soldiers stationed in Tianad soon disappeared. 36 | 37 | \medskip 38 | 39 | But amidst the resignation and fear, there's still hope. Adventurers of all professions gather in the guilds and inns all over Tianad, wondering what can be done about the situation. Wondering why there is no word from the borders. Wondering what happened to the king's Dragon Guard. Wondering where the monster gangs are coming from. 40 | 41 | And now, there's a rumour going around that in the remote city of Foxhome, a band of brave and intrepid adventurers has gathered to head off and find answers to all of these questions\dots and to save the once prosperous province of Tianad. 42 | 43 | \chapter{Introduction} 44 | Thank you very much for purchasing \textit{Dragon Rock}. We hope that you have as much fun playing as we had creating it. In order to have the best possible experience, please read this manual carefully. 45 | 46 | \section*{How to read this manual} 47 | 48 | This manual has been provided for beginners and experienced users alike. If you have no idea what computer role playing gaming is about, you're encouraged to read the entire manual. 49 | 50 | \begin{figure}[ht] 51 | \centering 52 | \includegraphics[width=0.5\textwidth]{illustrations/dormouse.jpg} 53 | \end{figure} 54 | 55 | If you have previous experience with computer role playing games, you may skip chapters two and three and instead begin directly with chapter 4. 56 | 57 | 58 | \section*{Requirements} 59 | \textit{Dragon Rock} is a fantasy role plaiyng game designed for the MEGA65. In order to run \textit{Dragon Rock}, you will need the following: 60 | \begin{itemize} 61 | \item A MEGA65 computer with at least \textbf{128K of RAM}. Please note that the game makes use of the MEGA65s advanced features and therefore will not work on an ordinary C64. 62 | \item A Commodore 1581 or compatible drive. 63 | \item A monochrome or -- recommended -- colour monitor. 64 | \end{itemize} 65 | 66 | \section*{Before you start} 67 | Before starting your journey through Tianad, please do take the time and make a backup of the supplied game disc. 68 | 69 | \section*{If things go wrong} 70 | A lot of time and effort went into the creation of \textit{Dragon Rock}. Nevertheless we can't be sure that the game is entirely bug-free. Should you happen to stumble upon an error in the game (or get stuck in any other way), please contact us at \texttt{dr@7turtles.de} 71 | 72 | 73 | \chapter{How to play} 74 | 75 | \section*{Starting the game} 76 | To start the game, power up your computer and disc drive, insert the Dragon Rock Program Disc into the drive and type 77 | \begin{verbatim} 78 | BOOT 79 | \end{verbatim} 80 | followed by the \texttt{RETURN} key. After the file has loaded successfully and the computer displays \texttt{READY}, type in 81 | \begin{verbatim} 82 | RUN 83 | \end{verbatim} 84 | again followed by the \texttt{RETURN} key to start the game. 85 | 86 | \section*{Setting up a party} 87 | On the first screen of the game, you're given the choice to either load a saved game or start in the village of Foxhome: 88 | 89 | \begin{figure}[ht] 90 | \centering 91 | \includegraphics[width=0.5\textwidth]{startscreen} 92 | \caption{Start screen} 93 | \end{figure} 94 | 95 | Since you'll have no saved game in the beginning, choose \textbf{option number two}\footnote{It doesn't actually matter which option you choose at this point; both options will place you in Foxhome with an empty party roster} to enter Foxhome. After a short time of loading, you will be presented with the following screen: 96 | 97 | \begin{figure}[ht] 98 | \centering 99 | \includegraphics[width=0.5\textwidth]{emptyCity} 100 | \caption{Starting in Foxhome} 101 | \end{figure} 102 | 103 | The empty area in the upper half of the screen is the space where the individual members of your party are listed. Since you have no party as of yet, this space remains empty. 104 | 105 | The city\footnote{Foxhome is one of Tianads seven major villages. While most services, such as the Adventurers' Guild, the Armory and the Inn, are available in all of Tianads villages, some villages may offer things that other villages don't\dots} is where your party will, among other things, buy and sell their stuff, as well as rest, heal and train. We'll look at the city in greater detail in one of the following chapters; for now, let's hop over to the \textbf{Adventurers Guild} to recruit some new heroes. 106 | 107 | \end{document} -------------------------------------------------------------------------------- /tools/png2dbm.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | 3 | ####################################################################### 4 | # png2dbm version 1.0 # 5 | # written by stephan kleinert @ hundehaus im reinhardswald, june 2021 # 6 | # with very special thanks to frau k., buba k. and candor k.! # 7 | ####################################################################### 8 | 9 | import sys 10 | from zlib import compress 11 | import png 12 | import io 13 | 14 | gVerbose = False 15 | gReserve = False 16 | gCompress = False 17 | gExcludePalette = False 18 | gVersion = "1.0" 19 | gPreserveBackgroundColour = False 20 | 21 | def vprint(*values): 22 | global gVerbose 23 | if gVerbose == True: 24 | print(*values) 25 | 26 | 27 | def showUsage(): 28 | print("usage: "+sys.argv[0]+" [-rv] infile outfile") 29 | print("convert PNG to MEGA65 DBM file") 30 | print("options: -r reserve system palette entries") 31 | print(" -0 preserve background colour") 32 | print(" -x exclude palette data") 33 | print(" -v verbose output") 34 | print(" -c compress output") 35 | exit(0) 36 | 37 | 38 | def nybswap(i): 39 | lownyb = i % 16 40 | hinyb = i // 16 41 | out = (lownyb*16)+hinyb 42 | return out 43 | 44 | 45 | def parseArgs(): 46 | global gReserve, gVerbose, gCompress, gExcludePalette, gPreserveBackgroundColour 47 | args = sys.argv.copy() 48 | args.remove(args[0]) 49 | fileargcount = 0 50 | 51 | for arg in args: 52 | if arg[0:1] == "-": 53 | opts = arg[1:] 54 | for opt in opts: 55 | if opt == "r": 56 | gReserve = True 57 | elif opt == "v": 58 | gVerbose = True 59 | elif opt == "c": 60 | gCompress = True 61 | elif opt == "x": 62 | gExcludePalette = True 63 | elif opt == "0": 64 | gPreserveBackgroundColour = True 65 | else: 66 | print("Unknown option", opt) 67 | showUsage() 68 | else: 69 | fileargcount += 1 70 | if fileargcount == 1: 71 | infile = arg 72 | elif fileargcount == 2: 73 | outfile = arg 74 | else: 75 | print("too many arguments") 76 | showUsage() 77 | return infile, outfile 78 | 79 | def pngRowsToM65Rows(pngRows): 80 | pngX = 0 81 | pngY = 0 82 | height = len(pngRows) 83 | width = len(pngRows[0]) 84 | columnCount = width//8 85 | rowCount = height//8 86 | vprint("using", rowCount, "rows,", columnCount, "columns.") 87 | m65Rows = [] 88 | for i in range(rowCount): 89 | aRow = [] 90 | for b in range(columnCount): 91 | aChar = bytearray() 92 | for c in range(64): 93 | aChar.append(0) 94 | aRow.append(aChar) 95 | m65Rows.append(aRow) 96 | for currentRow in pngRows: 97 | pngX = 0 98 | for currentColumn in currentRow: 99 | if gReserve: 100 | if gPreserveBackgroundColour: 101 | if currentColumn!=0: 102 | currentColumn += 16 103 | else: 104 | currentColumn += 16 105 | m65X = pngX//8 106 | m65Y = pngY//8 107 | m65Byte = ((pngX%8)+(pngY*8)) % 64 108 | m65Rows[m65Y][m65X][m65Byte] = currentColumn 109 | pngX += 1 110 | pngY += 1 111 | 112 | imageData = bytearray() 113 | for currentRow in m65Rows: 114 | for currentColumn in currentRow: 115 | imageData.extend(currentColumn) 116 | return imageData, rowCount, columnCount 117 | 118 | 119 | def rle(data): 120 | outdata = [] 121 | dsize = len(data) 122 | i = 0 123 | while i < dsize: 124 | current = data[i] 125 | count = 1 126 | if i < dsize: 127 | j = i+1 128 | while (j < dsize-1) and (data[j] == current and (count < 255)): 129 | count += 1 130 | j += 1 131 | if count == 1: 132 | outdata.append(current) 133 | i += 1 134 | else: 135 | outdata.append(current) 136 | outdata.append(current) 137 | outdata.append(count) 138 | i = j 139 | # print(outdata) 140 | return outdata 141 | 142 | 143 | ####################### main program ######################## 144 | 145 | 146 | 147 | 148 | # print(rle(["a", "b", "c", "c", "c", "d", "e"])) 149 | # exit(0) 150 | 151 | inputFileName, outputFileName = parseArgs() 152 | 153 | vprint("### png2dbm v"+gVersion+" ###") 154 | vprint("reading", inputFileName) 155 | pngReader = png.Reader(filename=inputFileName) 156 | pngData = pngReader.read() 157 | pngInfo = pngData[3] 158 | 159 | gWidth = pngInfo["size"][0] 160 | gHeight = pngInfo["size"][1] 161 | 162 | vprint("infile size is ", gWidth, "x", gHeight, "pixels") 163 | 164 | try: 165 | palette = pngInfo["palette"] 166 | except: 167 | print("error: infile has no palette") 168 | exit(1) 169 | 170 | 171 | vic4_palette = [] 172 | 173 | if gExcludePalette: 174 | vprint("excluding palette data") 175 | else: 176 | if gReserve: 177 | if len(palette) > 240: 178 | print("error: can't reserve system palette because source PNG " 179 | "has >240 palette entries.") 180 | exit(2) 181 | 182 | # add placeholders for system colours 183 | vprint("reserving system colour space") 184 | if gPreserveBackgroundColour: 185 | maxC = 15 186 | else: 187 | maxC = 16 188 | for i in range(maxC): 189 | vic4_palette.append((0, 0, 0)) 190 | 191 | # swap nyybles in palette 192 | vprint("swapping nybbles for", len(palette), "palette entries") 193 | for i in palette: 194 | r = nybswap(i[0]) 195 | g = nybswap(i[1]) 196 | b = nybswap(i[2]) 197 | vic4_palette.append((r, g, b)) 198 | 199 | vprint("outfile has",len(vic4_palette),"palette entries") 200 | 201 | rows = list(pngData[2]) 202 | imageData, numRows, numColumns = pngRowsToM65Rows(rows) 203 | m65data = bytearray() 204 | 205 | vprint("building outfile") 206 | m65data.extend(map(ord, 'DBMP')) # 0-3 : identifier bytes for format 207 | m65data.append(0x01) # 4 : version 208 | m65data.append(numRows) # 5 : number of rows 209 | m65data.append(numColumns) # 6 : number of columns 210 | # 7 : options (b0: RLE compressed; b1: sys palette reserved) 211 | m65data.append(gCompress+(2*gReserve)) 212 | m65data.append(len(vic4_palette)) # 8 : palette size 213 | 214 | if not gExcludePalette: 215 | for entry in vic4_palette: 216 | m65data.extend(entry) 217 | 218 | m65data.extend(map(ord,'IMG')) 219 | 220 | if gCompress: 221 | m65data.extend(rle(imageData)) 222 | else: 223 | m65data.extend(imageData) 224 | 225 | outfile = open(outputFileName, "wb") 226 | outfile.write(m65data) 227 | vprint("done.") 228 | outfile.close() 229 | -------------------------------------------------------------------------------- /src/dungeonLoader.c: -------------------------------------------------------------------------------- 1 | #include "congui.h" 2 | #include "dungeon.h" 3 | #include "globals.h" 4 | #include "memory.h" 5 | #include 6 | //#include 7 | #include 8 | #include 9 | #include 10 | 11 | #ifdef DEBUG 12 | #define DLDEBUG 13 | #endif 14 | 15 | #undef DLDEBUG 16 | 17 | const himemPtr externalDungeonAddr= ATTIC_DUNGEON_DATA; 18 | himemPtr seenMap; 19 | 20 | // clang-format off 21 | #pragma code-name(push, "OVERLAY1"); 22 | // clang-format on 23 | 24 | void buildFeelsTable(himemPtr *startAddr, dungeonDescriptor *desc, 25 | unsigned int numFeels); 26 | 27 | void buildDaemonsTable(himemPtr *startAddr, dungeonDescriptor *desc); 28 | 29 | // check for correct segment header, get number of elemnts and increase 30 | // himem pointer 31 | unsigned int verifySegment(himemPtr *adr, char *segmentID) { 32 | long count; 33 | long countAdr; 34 | #ifdef DLDEBUG 35 | cg_printf("=== looking for segment %s\n", segmentID); 36 | #endif 37 | countAdr= (*adr) + (strlen(segmentID)); 38 | count= lpeek(countAdr) + (256 * lpeek(countAdr + 1)); // get count for later 39 | lpoke(*adr + strlen(segmentID), 0); // end of string marker 40 | lcopy(*adr, (long)drbuf, 16); 41 | if (strcmp(segmentID, drbuf) != 0) { 42 | cg_printf("fatal: marker %s found, %s expected\n", drbuf, segmentID); 43 | while(1); 44 | } 45 | *adr+= strlen(segmentID) + 2; 46 | return count; 47 | } 48 | 49 | dungeonDescriptor *loadMap(char *filename) { 50 | 51 | unsigned int dungeonSize; 52 | unsigned int numFeels; 53 | unsigned int numOpcs; 54 | unsigned int numDaemons; 55 | 56 | byte i= 0; 57 | 58 | himemPtr currentExternalDungeonPtr; 59 | dungeonDescriptor *desc; 60 | int smSize; 61 | 62 | #ifdef DLDEBUG 63 | byte *debugPtr; 64 | #endif 65 | 66 | byte bytesRead; 67 | 68 | FILE *infile; 69 | 70 | #ifdef DLDEBUG 71 | cg_clrscr(); 72 | cg_printf("load map %s\n\nloading map header\n", filename); 73 | #endif 74 | 75 | mega65_io_enable(); 76 | currentExternalDungeonPtr= externalDungeonAddr; 77 | 78 | 79 | infile= fopen(filename, "rb"); 80 | 81 | if (!infile) { 82 | cg_fatal("file not found: %s",filename); 83 | } 84 | 85 | fread(drbuf, 3, 1, infile); 86 | drbuf[3]= 0; 87 | 88 | #ifdef DLDEBUG 89 | cg_printf("identifier segment: %s\n", drbuf); 90 | #endif 91 | 92 | if (strcmp(drbuf, "dr0") != 0) { 93 | cg_printf("?fatal: wrong map file format"); 94 | fclose(infile); 95 | while(1); 96 | } 97 | 98 | desc= (dungeonDescriptor *)malloc(sizeof(dungeonDescriptor)); 99 | 100 | #ifdef DLDEBUG 101 | cg_printf("dungeon descriptor: %x\n", desc); 102 | #endif 103 | 104 | fread(&dungeonSize, 2, 1, infile); 105 | 106 | while (!feof(infile)) { 107 | ++i; 108 | bytesRead= fread(drbuf, 1, DRBUFSIZE, infile); 109 | if (!(i % 8)) 110 | cg_putc('.'); 111 | lcopy((long)drbuf, currentExternalDungeonPtr, DRBUFSIZE); 112 | currentExternalDungeonPtr+= bytesRead; 113 | } 114 | 115 | fclose(infile); 116 | cg_go16bit(0,0); 117 | 118 | #ifdef DLDEBUG 119 | cg_printf("\nread mapdata up to %lx\n", currentExternalDungeonPtr); 120 | #endif 121 | 122 | // get dungeon header from high memory... 123 | lcopy(externalDungeonAddr, (long)drbuf, 4); 124 | 125 | // ...and fill in descriptions 126 | desc->dungeonMapWidth= *drbuf; 127 | desc->dungeonMapHeight= *(drbuf + 1); 128 | desc->dungeon= externalDungeonAddr + 2; 129 | desc->mapdata= externalDungeonAddr; 130 | 131 | #ifdef DLDEBUG 132 | cg_printf("dungeon at %lx\n", desc->dungeon); 133 | #endif 134 | 135 | smSize= desc->dungeonMapWidth * desc->dungeonMapHeight; 136 | lfill(SEENMAP_BASE,255,smSize); 137 | seenMap = SEENMAP_BASE; 138 | 139 | 140 | #ifdef DLDEBUG 141 | cg_printf("dungeon size %x, width %d, height %d.\n", dungeonSize, 142 | desc->dungeonMapWidth, desc->dungeonMapHeight); 143 | cg_printf("seen map is at $%lx, size $%x\n", seenMap, smSize); 144 | #endif 145 | 146 | // feels start behind end of dungeon 147 | currentExternalDungeonPtr= 148 | externalDungeonAddr + 2 + 149 | (desc->dungeonMapWidth * desc->dungeonMapHeight * 2); 150 | 151 | // -- FEELS -- 152 | numFeels= verifySegment(¤tExternalDungeonPtr, "feels"); 153 | buildFeelsTable(¤tExternalDungeonPtr, desc, numFeels); 154 | 155 | // -- DAEMS -- 156 | numDaemons= verifySegment(¤tExternalDungeonPtr, "daems"); 157 | desc->numDaemons= numDaemons; 158 | buildDaemonsTable(¤tExternalDungeonPtr, desc); 159 | 160 | // -- OPCS -- 161 | numOpcs= verifySegment(¤tExternalDungeonPtr, "opcs"); 162 | desc->opcodesAdr= currentExternalDungeonPtr; 163 | 164 | #ifdef DLDEBUG 165 | cg_printf("%d feels\n", numFeels); 166 | cg_printf("%d opcodes at %lx\n", numOpcs, desc->opcodesAdr); 167 | cg_printf("%d opcodes remaining.\n", 255 - numOpcs); 168 | debugPtr= (byte *)malloc(8); 169 | cg_printf("dungeon: %x-%x (size %x)\n", (int)desc, (int)debugPtr, 170 | (int)debugPtr - (int)desc); 171 | free(debugPtr); 172 | cg_getkey(); 173 | #endif 174 | 175 | return desc; 176 | } 177 | 178 | void buildDaemonsTable(himemPtr *startAddr, dungeonDescriptor *desc) { 179 | 180 | unsigned int size; 181 | byte i; 182 | daemonEntry *table; 183 | 184 | if (desc->numDaemons==0) { 185 | desc->daemonTbl=NULL; 186 | return; 187 | } 188 | 189 | size= (sizeof(daemonEntry) * (desc->numDaemons)); 190 | table= (daemonEntry *)malloc(size); 191 | lcopy(*startAddr, (long)table, size); 192 | *startAddr+= size; 193 | 194 | for (i=0;inumDaemons;++i) { 195 | cg_printf("daemon %d: %d %d %d %d opc %d",i,table[i].x1,table[i].y1, 196 | table[i].x2,table[i].y2,table[i].opcodeIndex); 197 | } 198 | 199 | desc->daemonTbl= table; 200 | } 201 | 202 | /** 203 | * @brief iterate over strings in himem and build pointer table to it 204 | * 205 | * @param startAddr start address in himem 206 | * @param desc dungeon descriptor 207 | * @param numFeels number of feels to index (part of map file) 208 | */ 209 | void buildFeelsTable(himemPtr *startAddr, dungeonDescriptor *desc, 210 | unsigned int numFeels) { 211 | 212 | himemPtr currentPtr; 213 | unsigned int currentFeelIdx; 214 | 215 | #ifdef DLDEBUG 216 | cg_printf("\nbuilding feels tbl "); 217 | #endif 218 | currentPtr= *startAddr; 219 | currentFeelIdx= 0; 220 | desc->feelTbl= (himemPtr *)malloc(sizeof(himemPtr) * numFeels); 221 | 222 | #ifdef DLDEBUG 223 | cg_printf("at %x in main mem\n", desc->feelTbl); 224 | // cgetc(); 225 | #endif 226 | 227 | while (currentFeelIdx < numFeels) { 228 | desc->feelTbl[currentFeelIdx]= currentPtr; 229 | #ifdef DLDEBUG 230 | cg_putc('.'); 231 | // cg_printf("%d-%lx ", currentFeelIdx, currentPtr); 232 | #endif 233 | while (lpeek(currentPtr) != 0) { 234 | currentPtr++; 235 | } 236 | 237 | currentFeelIdx++; 238 | currentPtr++; 239 | } 240 | *startAddr= currentPtr; 241 | } 242 | 243 | // clang-format off 244 | #pragma code-name(pop); 245 | // clang-format on 246 | -------------------------------------------------------------------------------- /doc/JITSA.md: -------------------------------------------------------------------------------- 1 | 2 | # JITSA 3 | 4 | This project was proudly brought to you using the JITSA development model. 5 | 6 | You may employ JITSA for your own projects free of charge. Once you have completed a project using the JITSA model, you may even call yourself "certified JITSA master" without spending a thousand $$$ on useless certificate hocus pocus. All I am asking is that you kindly include this file along with any released JITSA-enabled project, so that other people may learn of the power of JITSA, too. 7 | 8 | ## "That's great, but just what is JITSA?" 9 | 10 | JITSA is a software development model, just like Scrum. Or rather, not like Scrum at all. 11 | 12 | JITSA is the abbreviation for "**J**ust **I**mplement **T**hat **S**hit **A**lready". It was conceived during hours and hours of meaningless meetings, retros, refinements, blame games and other assorted what-have-yous, burning tons of company money and getting nowhere slowly. 13 | 14 | It is rooted in the realization that in modern software development methodologies, people often rather spend their time applying and discussing all kinds of rules and rituals than acutally getting any work done. Or, even worse: People who never ever wrote one single line of useful code debate on hours without end about how an application should be written and then force some poor code slaves to do some meaningless and unfulfilling work. 15 | 16 | Scrum & friends surely come with the best intentions, but unfortunately they encourage such behaviour, especially when applied the wrong way (which is the common way agile methodologies are applied nowadays) 17 | 18 | If you - like me - have learned your craft back in the golden days of writing C64 and Amiga Intros and hacking your way into the newly invented WWW, this mode of "work" can be terribly frustrating, to say the least. I've been a software developer for nearly 30 years now, and I have witnessed my trade getting mutilated and devalued by all kinds of religiously applied methodology bullshit. So I tried to come up with an alternative to the so-called 12 principles of agile programming... by writing my own 12 principles which address some of the things that went horribly wrong in today's "agile" development techniques. 19 | 20 | Here they are (for easier reference, they are presented next to their corresponding "principle of agile programming"): 21 | 22 | ## Agile vs. JITSA principles 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 |
AgileJITSA
1. Our highest priority is to satisfy the customer through early and continuous delivery of valuable software. 1. Our highest priority is keeping passionate about implementing some awesome shit. Customers suck. There's no customer. Because we're doing this shit exclusively for ourselves, and not for some puffed up suits who have no idea what the world needs or wants.
2. Welcome changing requirements, even late in development. Agile processes harness change for the customer's competitive advantage. 2. Requirement changes late in development are a telltale sign of clueless business people being involved in our shit. We don't want business people involved in our shit (see: 4)
3. Deliver working software frequently, from a couple of weeks to a couple of months, with a preference to the shorter timescale. 3. Deliver working shit when it's fucking ready. Not after that, and certainly not before that. If it takes another three months, so be it. There's no need to calm down product owners or investors with shiny "MVP" baubles or slide shows (which are nowadays called 'prototypes'). Just do what has to be done in the time that it takes.
4. Business people and developers must work together daily throughout the project.4. Absolutely no effing cooperation between business people and developers, because nobody needs business people. Yes, that's right, repeat after me: Nobody needs business people.
5. Build projects around motivated individuals. Give them the environment and support they need, and trust them to get the job done.5. Build projects around motivated individuals. Give them the environment and support they need, and trust them to get the job done.
6. The most efficient and effective method of conveying information to and within a development team is face-to-face conversation.6. The most efficient and effective method of giving motivated individuals the environment and support they need is leaving them the freedom to communicate however the hell they want to communicate
7. Working software is the primary measure of progress.7. Awesomeness is the primary measure of progress.
8. Agile processes promote sustainable development. The sponsors, developers, and users should be able to maintain a constant pace indefinitely.8. "Maintaining a constant pace" is for accountants and other overpaid losers with suits. If you need to sit down for three days to think about how to make this shit work, then by all means do just that. If it takes three weeks, well that's also fine. Don't worry and take all the time you need. And then just implement that shit already.
9. Continuous attention to technical excellence and good design enhances agility.9. "Technical excellence" and "good design" are buzzwords for people who don't know how to fill their time sheets. Todays good design is tomorrows legacy technology. Just implement that shit already, in the best way you can imagine.
10. Simplicity - the art of maximizing the amount of work not done - is essential.10. Just implementing that shit already (as opposed to reading long essays written by people who never coded even once in their lives) is essential. In fact, you shouldn't even be reading long essays written by people who spent all their live coding things. You should immediately get up and implement that shit already.
11. The best architectures, requirements, and designs emerge from self-organizing teams.11. The best architectures, requirements, and designs emerge from people who don't use words like "architectures", "requirements" and "designs" and rather just implement that shit already.
12. At regular intervals, the team reflects on how to become more effective, then tunes and adjusts its behavior accordingly.12. Just implement that shit already. And then use it yourself. Be happy and proud. Not being happy and proud with the way it turned out? Enhance it. Or throw it away and start from scratch. Or give up developing software and learn how to train horses. Or anything really. But don't let a set of rules force you to organize regular agile bible study groups and then talk about your feelings and writing them on coloured slips of paper.
78 | 79 | Of course JITSA is not really meant as an alternative to Scrum or Kanban or whatever it is they make you do. It doesn't work in every situation - in fact it probably won't work at all for most of the common scenarios. 80 | 81 | It's more of a guideline on how to take programming back into your own hands, and doing something awesome for yourself, like it was in the good old days... a feeling most of my younger colleagues - sadly - have never known. 82 | -------------------------------------------------------------------------------- /src/types.h: -------------------------------------------------------------------------------- 1 | /* 2 | * types.h 3 | * 4 | * Copyright (C) 2019 - Stephan Kleinert 5 | * 6 | * This program is free software; you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation; either version 2 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with this program. If not, see . 18 | */ 19 | 20 | #ifndef __drtypes 21 | #define __drtypes 22 | 23 | #define NUM_ATTRS 6 24 | #define INV_SIZE 12 25 | 26 | #define true 1 27 | #define false 0 28 | 29 | typedef unsigned char byte; 30 | typedef unsigned int word; 31 | 32 | typedef byte attrT; 33 | typedef byte itemT; 34 | typedef unsigned long himemPtr; 35 | 36 | typedef enum attribute { aSTR, aINT, aWIS, aDEX, aCON, aCHR } attrKind; 37 | 38 | typedef enum _gameMode { 39 | gm_init, 40 | gm_city, 41 | gm_outdoor, 42 | gm_dungeon, 43 | gm_encounter, 44 | gm_end 45 | } gameModeT; 46 | 47 | typedef enum _classT { 48 | ct_fighter, 49 | ct_ranger, 50 | ct_priest, 51 | ct_wizard, 52 | ct_thief 53 | } classT; 54 | 55 | typedef enum _raceT { rt_human, rt_dwarf, rt_elf, rt_halfelf, rt_gnome } raceT; 56 | 57 | typedef enum _encCommand { 58 | ec_nothing= 0, 59 | ec_thrust, 60 | ec_attack, 61 | ec_slash, 62 | ec_lunge, 63 | ec_spell, 64 | ec_parry, 65 | } encCommand; 66 | 67 | typedef enum _pcr { 68 | preCombatResultGreet, 69 | preCombatResultSurrender, 70 | preCombatResultMercy, 71 | preCombatResultFleeFailure, 72 | preCombatResultFleeSuccess, 73 | preCombatResultBeginFight, 74 | preCombatResultNoResponse 75 | } preCombatResult; 76 | 77 | typedef enum _encResult { 78 | encUndef= 0, // no encounter 79 | encWon, // the party wins 80 | encSurrender, // the monsters surrender 81 | encGreet, // both partys greet 82 | encFight, // encounter not over; monsters want to fight 83 | encMercy, // monsters show mercy 84 | encFled, // the party has fled 85 | encDead // the party is dead 86 | } encResult; 87 | 88 | typedef enum _cstateType { 89 | deleted= 0, 90 | down= 1, 91 | asleep= 2, 92 | dead= 3, 93 | awake= 4, 94 | charmed= 5, 95 | surrendered= 6 96 | } characterStateT; 97 | 98 | typedef enum _itemType { 99 | it_armor, 100 | it_shield, 101 | it_weapon, 102 | it_missile, 103 | it_potion, 104 | it_scroll, 105 | it_special 106 | } itemType; 107 | 108 | typedef enum _monstertype { 109 | mt_animal= 0x01, 110 | mt_humanoid= 0x02, 111 | mt_magical= 0x04, 112 | mt_unique= 0x08 113 | } monsterType; 114 | 115 | typedef enum _attackType { 116 | at_fists= 0x01, 117 | at_weapon= 0x02, 118 | at_fire= 0x04, 119 | at_ice= 0x08, 120 | at_claws= 0x10, 121 | at_drain= 0x20, 122 | at_breath= 0x40, 123 | at_spell= 0x80 124 | } attackType; 125 | 126 | typedef enum _spellClass { 127 | sc_priest= 0x01, 128 | sc_necromancer= 0x02, 129 | sc_battlemage= 0x04 130 | } spellClassT; 131 | 132 | typedef struct _cityCoords { 133 | byte mapNr; 134 | byte x; 135 | byte y; 136 | himemPtr cityName; 137 | himemPtr innName; 138 | himemPtr armoryOwnerName; 139 | } cityCoordsT; 140 | 141 | // --------------- spells ------------------ 142 | 143 | typedef enum _damageType { 144 | damageTypeMind, 145 | damageTypeFrost, 146 | damageTypeFire, 147 | damageTypeHealth, 148 | damageTypeSpecial 149 | } damageType; 150 | 151 | typedef struct _spell { 152 | char *name; 153 | byte spellLevel; 154 | byte minLevel; 155 | byte mpNeeded; 156 | byte minDmg; 157 | byte maxDmg; 158 | damageType dType; 159 | } spell; 160 | 161 | // -------------- equipment ---------------- 162 | 163 | typedef struct _item { // inventory item 164 | unsigned int id; 165 | unsigned int namePtr; 166 | itemType type; 167 | byte val1; // armor, weapon: minStrength needed; scroll: scrollID 168 | byte val2; // armor: ac value; weapon: hit dice 169 | byte val3; // weapon, armor, ring: bonus 170 | unsigned int price; 171 | } item; 172 | 173 | typedef struct _ditem { // dungeon item 174 | byte mapItem; 175 | byte opcodeID; 176 | } dungeonItem; 177 | 178 | typedef struct _opcode { 179 | byte id; 180 | byte param1; 181 | byte param2; 182 | byte param3; 183 | byte param4; 184 | byte param5; 185 | byte param6; 186 | byte param7; 187 | } opcode; 188 | 189 | typedef struct _cityDef { 190 | byte id; // 0 191 | byte mapNr; // 1 192 | byte x; // 2 193 | byte y; // 3 194 | unsigned int cityNamePtr; // 4+5 195 | unsigned int innNamePtr; // 6+7 196 | unsigned int armoryOwnerPtr; // 8+9 197 | } cityDef; 198 | 199 | typedef struct _monsterDef { 200 | unsigned int id; // 0-1 201 | byte level; // 2 202 | byte spriteID; // 3 203 | monsterType type; // 4 204 | unsigned int namePtr; // 5-6 205 | unsigned int pluralnamePtr; // 7-8 206 | signed char armorClass; // 9 207 | attackType aType[4]; // 10-13 208 | byte minDmg[4]; // 14-17 209 | byte maxDmg[4]; // 18-21 210 | signed char hitModifier[4]; // 22-25 211 | byte hpPerLevel; // 26 212 | byte mpPerLevel; // 27 213 | signed char courageModifier; // 28 214 | spellClassT spellClass; // 29 215 | int xpBaseValue; // 30-31 216 | } monsterDef; 217 | 218 | typedef struct monster { 219 | unsigned int monsterDefID; 220 | characterStateT status; 221 | byte hasDoneTurn; 222 | byte level; 223 | signed char row; 224 | signed char column; 225 | signed char initiative; 226 | int hp; 227 | int mp; 228 | } monster; 229 | 230 | typedef struct _character { 231 | characterStateT status; 232 | byte guildSlot; 233 | byte spriteID; 234 | byte city; 235 | byte level; 236 | int age; 237 | char name[16]; 238 | raceT aRace; 239 | classT aClass; 240 | attrT attributes[NUM_ATTRS]; 241 | byte spellMap[8]; 242 | int aMaxHP; 243 | int aMaxMP; 244 | int aHP; 245 | int aMP; 246 | int gold; 247 | int xp; 248 | itemT inventory[INV_SIZE]; 249 | itemT weapon; 250 | itemT shield; 251 | itemT armor; 252 | // encounter attributes 253 | signed char initiative; 254 | encCommand currentEncounterCommand; 255 | byte encSpell; 256 | byte encDestination; 257 | } character; 258 | 259 | typedef struct { 260 | byte x1, y1, x2, y2; 261 | unsigned int opcodeIndex; 262 | } daemonEntry; 263 | 264 | typedef struct _dungeonDescriptorS { 265 | himemPtr test; 266 | himemPtr mapdata; // external pointer to mapdata 267 | himemPtr dungeon; // external pointer to dungeon map 268 | himemPtr opcodesAdr; // external pointer to opcode list 269 | himemPtr *feelTbl; // pointer to message list (list of external pointers) 270 | daemonEntry *daemonTbl; // pointer to daemons table 271 | int numDaemons; // number of entries in daemons lookup table 272 | int numCoords; // number of entries in coords lookup table 273 | byte dungeonMapWidth; 274 | byte dungeonMapHeight; 275 | } dungeonDescriptor; 276 | 277 | typedef struct _hresult { // hit result 278 | byte success; 279 | byte critical; 280 | int hitRoll; 281 | int hitBonus; 282 | int damageBonus; 283 | int acHit; 284 | int toHit; 285 | int damage; 286 | character *theCharacter; 287 | monster *theMonster; 288 | } hitResult; 289 | 290 | #endif -------------------------------------------------------------------------------- /tools/genMonsters.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | 3 | import sys 4 | import pickle 5 | import csv 6 | import yaml 7 | 8 | monsterType = { 9 | "mt_humanoid": 1, 10 | "mt_animal": 2, 11 | "mt_magical": 4, 12 | "mt_unique": 128 13 | } 14 | 15 | spellClass = { 16 | "sc_priest": 1, 17 | "sc_necromancer": 2, 18 | "sc_battlemage": 4 19 | } 20 | 21 | attackTypes = { 22 | "at_fists": 1, 23 | "at_weapon": 2, 24 | "at_fire": 4, 25 | "at_ice": 8, 26 | "at_claws": 16, 27 | "at_drain": 32, 28 | "at_breath": 64, 29 | "at_spell": 128 30 | } 31 | 32 | 33 | def read(aFilename): 34 | with open(aFilename, 'r') as stream: 35 | try: 36 | monsterData = yaml.safe_load(stream) 37 | except yaml.YAMLError as exc: 38 | print(exc) 39 | exit(127) 40 | return monsterData 41 | 42 | 43 | def buildDescriptions(src): 44 | offsets = [] 45 | destbytes = bytearray() 46 | startMarker = "STRINGS*" 47 | destbytes.extend(map(ord, startMarker)) 48 | currentOffset = len(startMarker) 49 | for i in src: 50 | offsets.append(currentOffset) 51 | commobytes = bytearray() 52 | unixbytes = bytearray() 53 | i = i.replace("&&nl&&", "\n") 54 | unixbytes.extend(map(ord, i.swapcase())) 55 | for p in unixbytes: # lf -> crlf 56 | currentOffset += 1 57 | if (p == 10): 58 | commobytes.append(13) 59 | else: 60 | commobytes.append(p) 61 | currentOffset += 1 62 | commobytes.append(0) 63 | destbytes.extend(commobytes) 64 | return destbytes, offsets 65 | 66 | 67 | def reduceCity(aCity): 68 | global ids 69 | global names 70 | i = aCity 71 | cityID = i["id"] 72 | if cityID in ids: 73 | print("Error: duplicate city ID in", i) 74 | exit(1) 75 | 76 | names.append(i["cityName"]) 77 | i["nameIdx"] = len(names)-1 78 | names.append(i["innName"]) 79 | i["innNameIdx"] = len(names)-1 80 | names.append(i["armoryOwnerName"]) 81 | i["armoryOwnerNameIdx"] = len(names)-1 82 | return i 83 | 84 | 85 | def reduceMonster(aMonster): 86 | global ids 87 | global names 88 | i = aMonster 89 | monsterID = i["id"] 90 | if monsterID in ids: 91 | print("Error: Duplicate monster ID in", i) 92 | exit(1) 93 | 94 | names.append(i["name"]) 95 | i["name"] = len(names)-1 96 | 97 | if "pluralName" in i: 98 | names.append(i["pluralName"]) 99 | i["pluralName"] = len(names)-1 100 | 101 | atype = [] 102 | for at in i["attackTypes"]: 103 | atype.append(attackTypes[at]) 104 | 105 | i["attackTypes"] = atype 106 | 107 | mtype = 0 108 | for mt in i["monsterType"]: 109 | mtype += monsterType[mt] 110 | i["monsterType"] = mtype 111 | 112 | sclass = 0 113 | if "spellClass" in i: 114 | for sc in i["spellClass"]: 115 | sclass += spellClass[sc] 116 | i["spellClass"] = sclass 117 | 118 | while len(i["attackTypes"]) < 4: 119 | i["attackTypes"].append(0) 120 | 121 | while len(i["minDamage"]) < 4: 122 | i["minDamage"].append(0) 123 | 124 | while len(i["maxDamage"]) < 4: 125 | i["maxDamage"].append(0) 126 | 127 | while len(i["hitModifier"]) < 4: 128 | i["hitModifier"].append(0) 129 | 130 | # print (i) 131 | return i 132 | 133 | 134 | def reduceSpells(aSpell): 135 | global ids 136 | global names 137 | i = aSpell 138 | 139 | spellID = i["id"] 140 | if spellID in ids: 141 | print("Error: Duplicate spell ID in", i) 142 | exit(1) 143 | 144 | 145 | def toSpells(data): 146 | global ids 147 | global names 148 | ids = [] 149 | names = [] 150 | spells = [] 151 | 152 | spells.extend(map(reduceSpells,data)) 153 | 154 | 155 | 156 | def toCities(data): 157 | global ids 158 | global names 159 | ids = [] 160 | names = [] 161 | cities = [] 162 | 163 | cities.extend(map(reduceCity, data)) 164 | descbytes, offsets = buildDescriptions(names) 165 | 166 | cityMarker = "DRCITY00" 167 | outbytes = bytearray() 168 | cityRecordLength = 10 # keep in sync with monsterDef struct! 169 | stringsBase = len(cityMarker)+(len(cities)*cityRecordLength) 170 | 171 | outbytes = bytearray() 172 | 173 | outbytes.extend(map(ord, cityMarker)) 174 | 175 | for i in cities: 176 | cityNameOffset = stringsBase+offsets[i["nameIdx"]] 177 | innNameOffset = stringsBase+offsets[i["innNameIdx"]] 178 | armoryOwnerNameOffset = stringsBase+offsets[i["armoryOwnerNameIdx"]] 179 | outbytes.append(i["id"]) # 0 180 | outbytes.append(i["mapNr"]) # 1 181 | outbytes.append(i["x"]) # 2 182 | outbytes.append(i["y"]) # 3 183 | outbytes.append(cityNameOffset % 256) #4 184 | outbytes.append(cityNameOffset//256) #5 185 | outbytes.append(innNameOffset % 256) #6 186 | outbytes.append(innNameOffset//256) #7 187 | outbytes.append(armoryOwnerNameOffset % 256) #8 188 | outbytes.append(armoryOwnerNameOffset//256) #9 189 | outbytes.extend(descbytes) 190 | 191 | print(outbytes) 192 | return outbytes 193 | 194 | 195 | def toMonsters(data): 196 | global ids 197 | global names 198 | 199 | ids = [] 200 | names = [] 201 | monsters = [] 202 | 203 | monsters.extend(map(reduceMonster, data)) 204 | descbytes, offsets = buildDescriptions(names) 205 | # print(descbytes, offsets) 206 | 207 | # replace desc index with offset 208 | 209 | itemMarker = "DRMONST0" 210 | monsterRecordLength = 32 # IMPORTANT: Keep in sync with C struct!! 211 | 212 | stringsBase = len(itemMarker)+(len(monsters)*monsterRecordLength) 213 | # print("Strings base is", hex(stringsBase)) 214 | 215 | outbytes = bytearray() 216 | outbytes.extend(map(ord, itemMarker)) 217 | 218 | for i in monsters: 219 | i["name"] = stringsBase + offsets[i["name"]] 220 | if "pluralName" in i: 221 | i["pluralName"] = stringsBase + offsets[i["pluralName"]] 222 | else: 223 | i["pluralName"] = 0 224 | 225 | # print(i) 226 | outbytes.append(i["id"] % 256) # 0-1 : id 227 | outbytes.append(i["id"]//256) 228 | outbytes.append(i["defaultLevel"]) # 2 : defaultLevel 229 | outbytes.append(i["spriteID"]) # 3 : spriteID 230 | outbytes.append(i["monsterType"]) # 4 : monster type 231 | outbytes.append(i["name"] % 256) # 5-6 : monster name 232 | outbytes.append(i["name"]//256) 233 | outbytes.append(i["pluralName"] % 256) # 7-8 : plural name 234 | outbytes.append(i["pluralName"]//256) 235 | if i["AC"] < 0: 236 | outbytes.append(i["AC"]+256) # 9 : armor class 237 | else: 238 | outbytes.append(i["AC"]) 239 | for c in i["attackTypes"]: # 10-13: attackTypes 240 | outbytes.append(c) 241 | for c in i["minDamage"]: # 14-17: minDamage 242 | outbytes.append(c) 243 | for c in i["maxDamage"]: # 18-21: maxDamage 244 | outbytes.append(c) 245 | for c in i["hitModifier"]: # 22-25: hitModifier 246 | if (c < 0): 247 | outbytes.append(c+256) 248 | else: 249 | outbytes.append(c) 250 | outbytes.append(i["hitPoints"]) # 26 : hit points per level 251 | outbytes.append(i["magPoints"]) # 27 : mag points per level 252 | if i["courageMod"] < 0: 253 | outbytes.append(i["courageMod"]+256) # 28 : courage modifier 254 | else: 255 | outbytes.append(i["courageMod"]) 256 | outbytes.append(i["spellClass"]) # 29 : spell class 257 | 258 | outbytes.append(i["xpValue"] % 256) # 30-31 : xp value 259 | outbytes.append(i["xpValue"]//256) 260 | 261 | outbytes.extend(descbytes) 262 | return outbytes 263 | 264 | 265 | print("DragonRock gamedata builder v0.1, (w) Stephan Kleinert, 2021/06") 266 | 267 | if len(sys.argv) < 3: 268 | print("usage: "+sys.argv[0]+" infile configDir") 269 | exit(1) 270 | 271 | srcFilename = sys.argv[1] 272 | configDir = sys.argv[2] 273 | 274 | 275 | configDict = read(srcFilename) 276 | if "monsters" in configDict: 277 | monsterData = toMonsters(configDict["monsters"]) 278 | else: 279 | print("?no monster node found in configuration file") 280 | exit(1) 281 | 282 | if "cities" in configDict: 283 | cityData = toCities(configDict["cities"]) 284 | else: 285 | print("?no city node found in configuration file") 286 | exit(1) 287 | 288 | if "spells" in configDict: 289 | spellData = toSpells(configDict["spells"]) 290 | else: 291 | # print("?no spells node found in configuration file") 292 | # exit(1) 293 | pass 294 | #TODO 295 | 296 | outfile = open(configDir+"/monsters", "wb") 297 | outfile.write(monsterData) 298 | print("Monster file written successfully.") 299 | outfile.close() 300 | 301 | outfile = open(configDir+"/cities","wb") 302 | outfile.write(cityData) 303 | print("City file written successfully.") 304 | outfile.close() -------------------------------------------------------------------------------- /src/city.c: -------------------------------------------------------------------------------- 1 | 2 | 3 | #include 4 | //#include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | #include "city.h" 11 | 12 | #include "armory.h" 13 | #include "character.h" 14 | #include "congui.h" 15 | #include "globals.h" 16 | #include "guild.h" 17 | #include "utils.h" 18 | 19 | #include "dungeon.h" 20 | #include "memory.h" 21 | #include "menu.h" 22 | 23 | #define CDESC_SIZE 20 24 | 25 | char cityDescBuf[CDESC_SIZE]; 26 | 27 | cityDef *getCityDef(byte cityID) { 28 | static cityDef def; 29 | byte i; 30 | for (i= 0; i < 255; ++i) { 31 | lcopy((long)citiesBase + 8 + (sizeof(cityDef) * i), (long)&def, 32 | sizeof(cityDef)); 33 | if (def.id == cityID) { 34 | return &def; 35 | } 36 | } 37 | cg_fatal("no city def for ID %d", cityID); 38 | return NULL; 39 | } 40 | 41 | char *getNameForCityDef(cityDef *def) { 42 | lcopy((long)(citiesBase + (def->cityNamePtr)), (long)cityDescBuf, 43 | CDESC_SIZE); 44 | return cityDescBuf; 45 | } 46 | 47 | char *getInnNameForCityDef(cityDef *def) { 48 | lcopy((long)(citiesBase + (def->innNamePtr)), (long)cityDescBuf, 49 | CDESC_SIZE); 50 | return cityDescBuf; 51 | } 52 | 53 | char *getArmorerNameForCityDef(cityDef *def) { 54 | lcopy((long)(citiesBase + (def->armoryOwnerPtr)), (long)cityDescBuf, 55 | CDESC_SIZE); 56 | return cityDescBuf; 57 | } 58 | 59 | // clang-format off 60 | #pragma code-name(push, "OVERLAY2"); 61 | #pragma rodata-name (push, "OVERLAY2") 62 | #pragma local-strings (push,on) 63 | // clang-format on 64 | 65 | const char *invError= "INVERR (%d)"; 66 | dbmInfo *cityDBM; 67 | dbmInfo *guildDBM; 68 | dbmInfo *innDBM; 69 | 70 | void runCityMenu(void); 71 | 72 | void leaveCityMode(void) { 73 | cityDef *def; 74 | free(guild); 75 | releaseArmory(); 76 | def= getCityDef(gCurrentCityIndex); 77 | gCurrentDungeonIndex= def->mapNr | 128; 78 | gStartXPos= def->x; 79 | gStartYPos= def->y; 80 | cg_freeGraphAreas(); 81 | prepareForGameMode(gm_outdoor); 82 | } 83 | 84 | void distributeSpoils(void) { 85 | byte i; 86 | byte sharePerMember[PARTYSIZE]; 87 | byte totalShares= 0; 88 | unsigned int moneyShare; 89 | unsigned int xpShare; 90 | if (partyMemberCount() == 0) { 91 | return; 92 | } 93 | cg_clrscr(); 94 | cg_borders(true); 95 | cg_revers(1); 96 | cg_putsxy(2, 3, "Distribute gold and experience\n\n"); 97 | cg_revers(0); 98 | cg_cursor(1); 99 | for (i= 0; i < partyMemberCount(); ++i) { 100 | cg_gotoxy(5, 6 + i); 101 | cg_printf("Shares for %-10s: ", party[i]->name); 102 | do { 103 | sharePerMember[i]= cg_getkey() - '0'; 104 | } while (sharePerMember[i] < 1 || sharePerMember[i] > 3); 105 | cg_putc('0' + sharePerMember[i]); 106 | totalShares+= sharePerMember[i]; 107 | } 108 | moneyShare= gPartyGold / totalShares; 109 | xpShare= gPartyExperience / totalShares; 110 | cg_gotoxy(0, 14); 111 | cg_printf("Each share is %u xp and %u coins.", moneyShare, xpShare); 112 | for (i= 0; i < partyMemberCount(); ++i) { 113 | party[i]->gold+= sharePerMember[i] * moneyShare; 114 | party[i]->xp+= sharePerMember[i] * xpShare; 115 | } 116 | cg_putsxy(1, 18, "--key--"); 117 | cg_getkey(); 118 | } 119 | 120 | void loadCityImages() { 121 | sprintf(drbuf, "city%d.dbm", gCurrentCityIndex + 1); 122 | cityDBM= cg_loadDBM(drbuf, NULL, NULL); 123 | sprintf(drbuf, "inn%d.dbm", gCurrentCityIndex + 1); 124 | innDBM= cg_loadDBM(drbuf, NULL, NULL); 125 | guildDBM= cg_loadDBM("guild1.dbm", NULL, NULL); 126 | } 127 | 128 | void enterCityMode(void) { 129 | cg_go16bit(0, 0); 130 | cg_clrscr(); 131 | cg_gotoxy(4, 12); 132 | cg_printf("Welcome to %s", getNameForCityID(gCurrentCityIndex)); 133 | loadCityImages(); 134 | initGuild(); 135 | initArmory(); 136 | if (gPartyExperience || gPartyGold) { 137 | distributeSpoils(); 138 | } 139 | runCityMenu(); 140 | leaveCityMode(); 141 | } 142 | 143 | void doInn(void) { 144 | char *innMenu[]= {"Rest", "Save game", "Leave", NULL}; 145 | 146 | static unsigned char leaveInn; 147 | static byte menuChoice; 148 | 149 | leaveInn= 0; 150 | 151 | while (!leaveInn) { 152 | cg_clrscr(); 153 | cg_borders(true); 154 | cg_textcolor(COLOR_GRAY2); 155 | cg_displayDBMInfo(innDBM, 1, 1); 156 | sprintf(drbuf, getInnNameForCityID(gCurrentCityIndex)); 157 | cg_revers(1); 158 | cg_center(18, 0, 40 - 18, drbuf); 159 | cg_revers(0); 160 | showCurrentParty(1, 17, false); 161 | cg_textcolor(COLOR_ORANGE); 162 | menuChoice= runMenu(innMenu, 18, 2, true, false); 163 | 164 | leaveInn= menuChoice == 2; 165 | } 166 | } 167 | 168 | void doGuild(void) { 169 | char *guildMenu[]= {"New guild member", 170 | "Purge guild member", 171 | "List guild members", 172 | "Add to party", 173 | "Drop from party", 174 | "Train", 175 | "Research spells", 176 | "Exit", 177 | NULL}; 178 | 179 | static unsigned char quitGuild; 180 | static byte menuChoice; 181 | 182 | quitGuild= 0; 183 | 184 | while (!quitGuild) { 185 | cg_clrscr(); 186 | cg_borders(true); 187 | cg_textcolor(COLOR_GRAY2); 188 | cg_displayDBMInfo(guildDBM, 1, 1); 189 | sprintf(drbuf, "%s guild", getNameForCityID(gCurrentCityIndex)); 190 | cg_revers(1); 191 | cg_center(18, 0, 40 - 18, drbuf); 192 | cg_revers(0); 193 | showCurrentParty(1, 17, false); 194 | cg_textcolor(COLOR_ORANGE); 195 | menuChoice= runMenu(guildMenu, 18, 2, true, false); 196 | 197 | if (menuChoice >= 100) { 198 | inspectCharacter(menuChoice - 100); 199 | } 200 | 201 | switch (menuChoice) { 202 | case 0: 203 | newGuildMember(gCurrentCityIndex); 204 | break; 205 | 206 | case 1: 207 | purgeGuildMember(); 208 | break; 209 | 210 | case 3: 211 | addToParty(); 212 | break; 213 | 214 | case 4: 215 | dropFromParty(); 216 | break; 217 | 218 | case 7: 219 | quitGuild= true; 220 | break; 221 | 222 | default: 223 | break; 224 | } 225 | } 226 | } 227 | 228 | void saveGame() { 229 | cg_clrscr(); 230 | cg_borders(true); 231 | cg_puts("\nPlease wait..."); 232 | saveArmory(); 233 | saveGuild(); 234 | saveParty(); 235 | cg_puts("\n\n...done.\n\n --key--"); 236 | cg_getkey(); 237 | } 238 | 239 | void runCityMenu(void) { 240 | 241 | static unsigned char cmd; 242 | static byte menuChoice; 243 | static unsigned char quitCity; 244 | char *cityMenu[]= {"Guild", "Armory", "Inn", "Bank", 245 | "Mystic", "Save", "Leave", NULL}; 246 | 247 | quitCity= 0; 248 | 249 | while (!quitCity) { 250 | cg_resetwin(); 251 | cg_clrscr(); 252 | cg_borders(true); 253 | cg_displayDBMInfo(cityDBM, 1, 1); 254 | showCurrentParty(1, 17, false); 255 | cg_gotoxy(3, 16); 256 | cg_textcolor(COLOR_GRAY3); 257 | cg_revers(1); 258 | cg_center(0, 16, 18, getNameForCityID(gCurrentCityIndex)); 259 | cg_revers(0); 260 | cg_textcolor(COLOR_CYAN); 261 | menuChoice= runBottomMenuN(cityMenu); 262 | cmd= 0; 263 | 264 | if (menuChoice >= 100) { 265 | inspectCharacter(menuChoice - 100); 266 | } else { 267 | 268 | switch (menuChoice) { 269 | 270 | case 0: 271 | doGuild(); 272 | break; 273 | 274 | case 1: 275 | doArmory(); 276 | break; 277 | 278 | case 2: 279 | doInn(); 280 | break; 281 | 282 | case 5: 283 | saveGame(); 284 | break; 285 | 286 | case 6: 287 | cg_clearBottomLine(); 288 | cg_gotoxy(0, gScreenRows - 1); 289 | cg_printf("Really leave %s (y/n)?", 290 | getNameForCityID(gCurrentCityIndex)); 291 | do { 292 | cg_cursor(1); 293 | cmd= cg_getkey(); 294 | cg_cursor(0); 295 | } while (strchr("yn", cmd) == NULL); 296 | if (cmd == 'y') { 297 | quitCity= 1; 298 | } 299 | cmd= 0; 300 | break; 301 | 302 | 303 | default: 304 | cg_fatal("not implemented"); 305 | break; 306 | } 307 | } 308 | } 309 | } 310 | 311 | void newGuildMember(byte city) { 312 | static byte i, c; // loop and input temp vars 313 | static byte race; 314 | static byte class; 315 | attrT tempAttr[6]; 316 | static attrT current; 317 | static signed char slot; 318 | int tempHP; 319 | int tempMP; 320 | char *cname; 321 | character *newC; // the "cr" sign to the finished string... 322 | 323 | static char top; // screen top margin 324 | 325 | const char margin= 14; 326 | const char delSpaces= 40 - margin; 327 | 328 | char *menuEntries[10]; 329 | 330 | cg_titlec(COLOR_CYAN, 0, "New Guild Member"); 331 | 332 | slot= nextFreeGuildSlot(); 333 | if (slot == -1) { 334 | cg_textcolor(2); 335 | cg_puts("\nSorry, the guild is full." 336 | "\nPlease purge some inactive members" 337 | "before creating new ones.\n\n--key--"); 338 | cg_getkey(); 339 | return; 340 | } 341 | 342 | top= 5; 343 | newC= &guild[slot]; 344 | 345 | cg_putsxy(2, top, " Race:"); 346 | for (i= 0; i < NUM_RACES; i++) { 347 | menuEntries[i]= gRaces[i]; 348 | } 349 | menuEntries[i]= NULL; 350 | race= runMenu(menuEntries, margin, top, true, false); 351 | 352 | for (i= top - 1; i < NUM_RACES + top + 3; 353 | cg_clearxy(margin, ++i, delSpaces)) 354 | ; 355 | cg_putsxy(margin, top, gRaces[race]); 356 | 357 | ++top; 358 | 359 | cg_putsxy(2, top, " Class:"); 360 | for (i= 0; i < NUM_CLASSES; i++) { 361 | menuEntries[i]= gClasses[i]; 362 | } 363 | menuEntries[i]= NULL; 364 | class= runMenu(menuEntries, margin, top, true, false); 365 | for (i= top - 1; i < NUM_CLASSES + top + 3; 366 | cg_clearxy(margin, ++i, delSpaces)) 367 | ; 368 | cg_putsxy(margin, top, gClasses[class]); 369 | 370 | top+= 2; 371 | 372 | cg_putsxy(2, top, "Attributes:"); 373 | do { 374 | for (i= 0; i < 6; i++) { 375 | current= 7 + (drand(12) + gRaceModifiers[race][i]); 376 | tempAttr[i]= current; 377 | cg_putsxy(margin, top + i, gAttributes[i]); 378 | cg_gotoxy(margin + 13, top + i); 379 | cg_printf("%2d %s", current, bonusStrForAttribute(current)); 380 | } 381 | tempHP= 3 + drand(8) + bonusValueForAttribute(tempAttr[0]); 382 | tempMP= 3 + drand(8) + bonusValueForAttribute(tempAttr[1]); 383 | 384 | cg_gotoxy(margin, top + i + 1); 385 | cg_printf("Hit points %2d", tempHP); 386 | 387 | cg_gotoxy(margin, top + i + 2); 388 | cg_printf("Magic points %2d", tempMP); 389 | 390 | menuEntries[0]= "keep"; 391 | menuEntries[1]= "reroll"; 392 | menuEntries[2]= "quit"; 393 | menuEntries[3]= NULL; 394 | 395 | c= runBottomMenu(menuEntries); 396 | 397 | } while (c == 1); 398 | 399 | if (c == 2) 400 | return; 401 | 402 | top= top + i + 4; 403 | cg_clearxy(1, top, 38); 404 | cg_putsxy(18, top + 1, "----------------"); 405 | cg_putsxy(2, top, "Character name: "); 406 | cname= cg_input(16); 407 | 408 | newC->city= city; 409 | newC->guildSlot= slot; 410 | newC->status= awake; 411 | newC->aRace= race; 412 | newC->aClass= class; 413 | 414 | for (i= 0; i < NUM_ATTRS; i++) { 415 | newC->attributes[i]= tempAttr[i]; 416 | } 417 | 418 | // empty inventory & known spells 419 | for (i= 0; i < INV_SIZE; i++) { 420 | newC->inventory[i]= 0; 421 | } 422 | for (i= 0; i < 8; i++) { 423 | newC->spellMap[i]= 0; 424 | } 425 | 426 | addInventoryItem(0xff, newC); // add white orb for testing 427 | newC->weapon= 0x01; // add club 428 | newC->armor= 0x80; // add robes 429 | newC->aMaxHP= tempHP; 430 | newC->aHP= tempHP; 431 | newC->aMaxMP= tempMP; 432 | newC->aMP= tempMP; 433 | newC->level= 1; 434 | newC->spriteID= 0x80 + newC->aRace; 435 | strcpy(newC->name, cname); 436 | free(cname); 437 | } 438 | 439 | // clang-format off 440 | #pragma code-name(pop); 441 | #pragma rodata-name (pop) 442 | #pragma local-strings (pop) 443 | // clang-format on 444 | -------------------------------------------------------------------------------- /maps/library.drs: -------------------------------------------------------------------------------- 1 | ; 2 | ; deserted library 3 | ; 4 | 5 | includemap "maps/library.drm" 6 | 7 | ; ========================= 8 | ; entrance & first corridor 9 | ; ========================= 10 | 11 | connectLabel entrance: 15,0 ; connects entrance 12 | connectLabel corridor: 15,1 - 15,17 ; corridor message 13 | connectLabel corridor_h: 3,17 - 25,17 ; horizontal corridor message 14 | connectLabel historySign: 15,3 15 | 16 | $ msgCorridor,"A long, eerie corridor." 17 | 18 | $ msgEntrance,""" 19 | Two wooden doors guard the entrance 20 | to the library. Do you want to leave? 21 | (yes/no):""" 22 | 23 | $ historySignMsg,""" 24 | A sign above the east door says: 25 | 'HISTORY AND SCIENCE' 26 | """ 27 | 28 | corridor_h: 29 | corridor: 30 | NSTAT msgCorridor 31 | --- 32 | 33 | entrance: 34 | DISP msgEntrance,True 35 | YESNO_B exitDungeon 36 | REDRAW 37 | --- 38 | 39 | historySign: 40 | NSTAT historySignMsg 41 | --- 42 | 43 | exitDungeon: 44 | ENTER_W 33,10,2 ; load outdoor map 33 45 | --- 46 | 47 | 48 | ; ============================ 49 | ; history & science rooms 50 | ; ============================ 51 | 52 | connectLabel historyRoom: 17,1 - 24,5 53 | connectLabel wallWriting1: 19,5 54 | connectLabel shelves1: 18,2 - 23,2 55 | connectLabel shelves2: 18,4 - 23,4 56 | connectLabel shelfCoins: 20,4 57 | connectLabel scienceDoorSpace: 24,3 58 | 59 | ; consecutive defcs to the same spot overwrite earlier defcs, 60 | ; but they have the virtue of creating a label which we can 61 | ; later use to alter the dungeon via the ALTER opcode. 62 | 63 | connectLabel historyRoomKobolds: 16,3 64 | connectLabel historyRoomKoboldsPrompt: 16,3 65 | 66 | $ historyRoomMsg,""" 67 | It seems like someone (or something) 68 | destroyed all of the books in a blind 69 | rage. Thousands of torn, shredded and 70 | burned pages cover the floor.""" 71 | 72 | $ msgKobolds1,""" 73 | 74 | The history and science sctions of 75 | the once great central library of 76 | Tianad. 77 | 78 | Thousands of torn, shredded and burned 79 | pages cover the floor and the shelves 80 | are mostly burnt down to the ground. 81 | 82 | """ 83 | 84 | $ msgKobolds2,""" 85 | 86 | Oh yes, there's also a group of 87 | bloodthirsty kobolds here, grinning at 88 | you, swords and clubs at the ready! 89 | 90 | """ 91 | 92 | $ historyRoomShelvesMsg,""" 93 | These big and mighty shelves once were 94 | home to many a beautiful tome, but 95 | now only dust and ashes remain.""" 96 | 97 | $ wallWritingMsg1,""" 98 | A grafitti on the south wall reads: 99 | 'OBEZNA KAN'T REED'""" 100 | 101 | $ foundGoldMsg,"Wait, there's some coins!\n" 102 | 103 | $ scienceDoorMsg,""" 104 | This door hangs broken in its hinges. 105 | A sign above it says 'SCIENCE', but 106 | below, someone scrawled on the door: 107 | 'OBEZNA WAS HAER'""" 108 | 109 | historyRoomKoboldsPrompt: 110 | DISP msgKobolds1,True 111 | WKEY msgWaitkey 112 | DISP msgKobolds2 113 | WKEY msgWaitkey 114 | ALTER historyRoomKoboldsPrompt,historyRoomKobolds,0 ; shows messages only once 115 | 116 | historyRoomKobolds: 117 | CLRENC 118 | ADDENC 1,1,2,3,0 119 | ADDENC 2,1,3,4,1 120 | ADDENC 1,1,4,5,2 121 | GOENC winH,loseH 122 | --- 123 | 124 | winH: 125 | ALTER historyRoomKoboldsPrompt, doNothing, 0 126 | --- 127 | 128 | loseH: 129 | WKEY msgWaitkey 130 | --- 131 | 132 | historyRoom: 133 | NSTAT historyRoomMsg 134 | --- 135 | 136 | shelves1: 137 | shelves2: 138 | NSTAT historyRoomShelvesMsg 139 | --- 140 | 141 | shelfCoins: 142 | DISP foundGoldMsg,True 143 | ADDC_V 60 144 | ALTER shelfCoins, shelves1, 1 145 | WKEY msgWaitkey 146 | REDRAW 147 | --- 148 | 149 | scienceDoorSpace: 150 | NSTAT scienceDoorMsg 151 | --- 152 | 153 | wallWriting1: 154 | NSTAT wallWritingMsg1 155 | --- 156 | 157 | ; ==================== 158 | ; great hall 159 | ; ==================== 160 | 161 | connectLabel greatHall: 17,11 - 30,15 162 | connectLabel crazyMan: 20,13 163 | 164 | ;----------------------------------- 165 | $ greatHallMsg,""" 166 | The great hall of the library. Four 167 | marble pillars in the center are all 168 | that has survived the burning and 169 | the looting. 170 | """ 171 | 172 | $ crazyManMsg,""" 173 | 174 | All of a sudden, a mangy, deranged 175 | figure jumps at you from behind the 176 | pillar, raving and drooling. 177 | 178 | 'They have no mercy, have no mercy!', 179 | he screams. Then he attacks. 180 | 181 | """ 182 | 183 | greatHall: 184 | NSTAT greatHallMsg 185 | --- 186 | 187 | crazyMan: 188 | DISP crazyManMsg,True 189 | WKEY msgWaitkey 190 | CLRENC 191 | ADDENC 1,1,1,1,0 192 | GOENC winCrazy,loseCrazy 193 | 194 | winCrazy: 195 | ALTER crazyMan,greatHall,0 196 | REDRAW 197 | --- 198 | 199 | loseCrazy: 200 | REDRAW 201 | --- 202 | 203 | 204 | ; ==================== 205 | ; reading rooms 206 | ; ==================== 207 | 208 | connectLabel readingRooms: 16,7 - 30,9 209 | 210 | ; subsequent defcs override previous defcs, 211 | ; so we can define the whole area as reading 212 | ; room and then carve out the corridor... 213 | 214 | connectLabel rrHallway1: 20,7 - 21,7 215 | connectLabel rrHallway2: 24,7 - 25,7 216 | connectLabel rrHallway3: 28,7 - 30,7 217 | connectLabel kickedDoor: 15,8 218 | 219 | $ kickedDoorMsg,""" 220 | The door to the east has been 221 | kicked in. 222 | """ 223 | 224 | $ readingRoomMsg,""" 225 | A small reading room with 226 | nothing much of interest. 227 | """ 228 | 229 | $ corridorMsg,""" 230 | A small corridor connecting 231 | the reading rooms. 232 | """ 233 | 234 | kickedDoor: 235 | NSTAT kickedDoorMsg 236 | --- 237 | 238 | rrHallway1: 239 | rrHallway2: 240 | rrHallway3: 241 | NSTAT corridorMsg 242 | --- 243 | 244 | readingRooms: 245 | NSTAT readingRoomMsg 246 | --- 247 | 248 | ; ==================== 249 | ; science room 250 | ; ==================== 251 | 252 | connectLabel scienceRoom: 26,1 - 30,4 253 | connectLabel alcove: 28,5 254 | 255 | $ scienceRoomMsg,""" 256 | Everything that once was in this 257 | room has been burned to ashes. You 258 | can still smell the fire.""" 259 | 260 | $ alcoveMsg,""" 261 | There is a small alcove here. In 262 | it, you find some items... 263 | """ 264 | 265 | $ alcoveMsg2, "There is a small alcove here." 266 | 267 | scienceRoom: 268 | NSTAT scienceRoomMsg 269 | --- 270 | 271 | alcoveEmpty: 272 | NSTAT alcoveMsg2 273 | --- 274 | 275 | alcove: 276 | DISP alcoveMsg,True 277 | WKEY msgWaitkey 278 | IADD_V 4,255 ; sword 279 | IADD_V 64,255 ; shield 280 | WKEY msgWaitkey 281 | ALTER alcove,alcoveEmpty,0 282 | REDRAW 283 | --- 284 | 285 | ; =========================== 286 | ; librarian's floor and room 287 | ; =========================== 288 | 289 | connectLabel librarianFloor: 1,6 - 13,6 290 | connectLabel librarianRoom: 3,1 - 8,4 291 | connectLabel librarianRoomDoor: 6,5 ; we include the door here for dramatic effect 292 | connectLabel librarianStart: 6,4 ; librarian stuff starts when entering the room 293 | connectLabel librarianAlcove: 2,1 294 | 295 | $ msgLibrarianFloor,"A surprisingly clean corridor." 296 | $ msgLibrarianRoom,"The librarian's room" 297 | 298 | $ msgLR1,""" 299 | This is the librarian's room. A lot of 300 | shelves and bookstands line the wall, 301 | and there's a magnificiant, wooden 302 | writing desk in the center of the 303 | room. 304 | 305 | Before it, on the ground, there's the 306 | librarian. It seems like he was badly 307 | wounded in the attack on the library, 308 | but somehow managed to work the secret 309 | doors and get to safety here. 310 | 311 | It looks like the librarian is still 312 | alive. What do you do? 313 | 314 | a) kill him 315 | b) search him 316 | c) help him 317 | 318 | """ 319 | 320 | $ msgKillLibrarian, "You kill the librarian.\n" 321 | 322 | $ msgLibrarianAlcove,"In a hidden alcove you find a scroll.\n\n" 323 | 324 | $ msgSearchLibrarian, """ 325 | The dying librarian looks at you in 326 | horror as you search him. He opens 327 | his mouth as if trying to tell you 328 | something. His eyes dart to the 329 | northwest corner of the room. 330 | 331 | Then he dies. 332 | 333 | """ 334 | 335 | $ msgHealLibrarian, """ 336 | You try to heal the librarian, but his 337 | wounds are too severe. 338 | 339 | Just before he dies, he makes a short 340 | but elegant motion with his hands and 341 | mutters an incantation. A scroll 342 | magically appears in his hands. 343 | 344 | 'She... destroyed... everything... 345 | take... take it...', he says with his 346 | dying breath. 347 | 348 | """ 349 | 350 | librarianFloor: 351 | NSTAT msgLibrarianFloor 352 | --- 353 | 354 | librarianRoom: 355 | librarianRoomDoor: 356 | NSTAT msgLibrarianRoom 357 | --- 358 | 359 | librarianStart: 360 | WKEY msgLR1, True, 1 361 | SETREG 5,0 362 | IFREG 1,65,doKillLibrarian ; these all set register 5 363 | IFREG 1,66,doSearchLibrarian ; so that when the choice is invalid 364 | IFREG 1,67,doHealLibrarian ; the script loops... 365 | IFREG_B 5,0,librarianStart ; ...back to the start. 366 | REDRAW 367 | ALTER librarianStart,librarianRoom,0,0 368 | --- 369 | 370 | doKillLibrarian: 371 | SETREG 5,1 372 | DISP msgKillLibrarian 373 | WKEY msgWaitkey 374 | --- 375 | 376 | doSearchLibrarian: 377 | SETREG 5,1 378 | DISP msgSearchLibrarian, True 379 | WKEY msgWaitkey 380 | --- 381 | 382 | doHealLibrarian: 383 | SETREG 5,1 384 | DISP msgHealLibrarian, True 385 | WKEY msgWaitkey 386 | ALTER librarianAlcove,doNothing,36 387 | IADD_V 160,255,doNothing,doNothing ; 160 = scroll 1 388 | ADDE_V 200 389 | WKEY msgWaitkey 390 | --- 391 | 392 | librarianAlcove: 393 | DISP msgLibrarianAlcove, True 394 | IADD_V 160,255,doNothing,doNothing 395 | ALTER librarianAlcove,doNothing,0 396 | WKEY msgWaitkey 397 | REDRAW 398 | --- 399 | 400 | 401 | ; ==================== 402 | ; locked storage room 403 | ; ==================== 404 | 405 | connectLabel storageRoomDoor: 14,2 406 | connectLabel storageRoom: 10,1 - 13,4 407 | connectLabel emptyBoxes: 10,1 - 11,4 408 | connectLabel lever: 10,4 409 | connectLabel leverDoor: 14,6 410 | 411 | $ msgStorageRoom,""" 412 | A surprisingly clean and kempt storage 413 | room. Lots of wooden boxes line the 414 | west wall""" 415 | 416 | $ msgEmptyBox,"Here is an empty wooden box." 417 | 418 | $ msgLeverInitial,""" 419 | Wait, there is a lever hidden behind 420 | this empty box! """ 421 | 422 | $ msgLever,"There is a lever here.\n" 423 | 424 | $ msgLeverUp,""" 425 | The lever is up. 426 | Pull it? (y/n)""" 427 | 428 | $ msgLeverDown,""" 429 | The lever is down. 430 | Push it? (y/n)""" 431 | 432 | $ msgLeverSound,"""\n 433 | You hear a rumbling sound 434 | in the distance.\n 435 | """ 436 | 437 | lever: 438 | DISP msgLeverInitial,True 439 | ALTER lever,leverUp,1 440 | GOTO lu2 441 | --- 442 | 443 | leverUp: 444 | DISP msgLever, True 445 | lu2: 446 | DISP msgLeverUp 447 | YESNO doLeverDown 448 | REDRAW 449 | --- 450 | 451 | leverDown: 452 | DISP msgLever,True 453 | DISP msgLeverDown 454 | YESNO doLeverUp 455 | REDRAW 456 | --- 457 | 458 | doLeverUp: 459 | ALTER lever, leverUp, 1 460 | ALTER leverDoor, doNothing, 36 ; close lever door (32=impassable | 4 =wall) 461 | DISP msgLeverSound 462 | WKEY msgWaitkey 463 | --- 464 | 465 | doLeverDown: 466 | ALTER lever, leverDown, 1 467 | ALTER leverDoor, doNothing, 0 ; open lever door 468 | DISP msgLeverSound 469 | WKEY msgWaitkey 470 | --- 471 | 472 | emptyBoxes: 473 | NSTAT msgEmptyBox 474 | --- 475 | 476 | storageRoom: 477 | NSTAT msgStorageRoom 478 | --- 479 | 480 | storageRoomDoor: 481 | IFPOS 240,openStorageRoomDoor,lockedDoor,15 482 | --- 483 | 484 | lockedDoor: 485 | NSTAT msgLockedDoor 486 | --- 487 | 488 | $ msgOpenSDoor, "Your rusty key opens the door." 489 | 490 | openStorageRoomDoor: 491 | NSTAT msgOpenSDoor 492 | --- 493 | 494 | ; 495 | ; ---------------------------------- 496 | ; secret room south of main corridor 497 | ; ---------------------------------- 498 | ; 499 | 500 | connectLabel secretDoor1: 21,18 ; secret door in start corridor 501 | connectLabel secretRoom1: 20,19 - 22,21 ; secret room 502 | connectLabel sRoomTable: 21,20 ; table in secret room 503 | connectLabel sWind: 20,17 504 | 505 | $ msgSecret1, "Wait, there's a secret door here!" 506 | 507 | $ msgWind, "There's a light draft coming from\nthe southeast" 508 | 509 | $ msgSecretRoom1, "A small, hidden room with a wooden\ntable in its center." 510 | 511 | $ msgTable1,""" 512 | There is a small, wooden table here. 513 | A rusty key is lying on top of it. 514 | Do you take the key (y/n)?""" 515 | 516 | $ msgTable2, "There is a small, wooden table here." 517 | 518 | sWind: 519 | NSTAT msgWind 520 | --- 521 | 522 | secretDoor1: 523 | NSTAT msgSecret1 524 | --- 525 | 526 | secretRoom1: 527 | NSTAT msgSecretRoom1 528 | --- 529 | 530 | sRoomTable: ; initial state of table 531 | DISP msgTable1,True 532 | YESNO takeKey 533 | REDRAW 534 | --- 535 | 536 | sRoomTableEmpty: ; table after key is taken 537 | NSTAT msgTable2 538 | --- 539 | 540 | takeKey: 541 | IADD_V 240,255,keyTaken,doNothing 542 | --- 543 | 544 | keyTaken: 545 | ; when key is taken, change table into empty table... 546 | ALTER sRoomTable, sRoomTableEmpty, 1 547 | WKEY msgWaitkey 548 | --- 549 | 550 | doNothing: 551 | NOP 552 | --- 553 | 554 | ; misc messages 555 | 556 | $ msgWaitkey, "-- press any key --\n" 557 | $ msgLockedDoor, "The door is locked." 558 | 559 | ; ------------------------------------- 560 | ; file messages 561 | ; ------------------------------------- 562 | 563 | & "gamedata/fmsg01",""" 564 | 565 | -------------------------- 566 | OBEZNA, DRAGON OF MISTRUST 567 | -------------------------- 568 | 569 | It is rumored that this evil beast has 570 | its lair somewhere in the southwestern 571 | granite caves, near the city of Hompart. 572 | There, Obezna commands a small army of 573 | kobolds to do her bidding. 574 | 575 | Usually, Obezna only leaves her home to 576 | do dirty work for other, more respected 577 | creatures of evil, but occasionally 578 | she can be seen scouring the countryside 579 | for food or shiny objects which she 580 | steals from merchant's caravans crossing 581 | the southern border of Hompart. 582 | 583 | Obezna is vile, mistrusting and bitter, 584 | and in an encounter, she always assumes 585 | that her opponents are just as deceitful 586 | and keen on stealing things as herself, 587 | which makes her a highly unstable 588 | negotiator and a fierce enemy. 589 | 590 | It has been suggested by some scholars 591 | that Obezna is close friends with 592 | Gerulda, the fabled great ego dragon. 593 | But since it has been established that 594 | ego dragons are not capable of real 595 | friendship, and because of Obeznas 596 | own distrust of everything, these ideas 597 | probably are a myth. """ -------------------------------------------------------------------------------- /src/character.c: -------------------------------------------------------------------------------- 1 | #include 2 | //#include 3 | #include 4 | #include 5 | #include 6 | 7 | #include "character.h" 8 | #include "congui.h" 9 | #include "globals.h" 10 | #include "memory.h" 11 | #include "menu.h" 12 | #include "spell.h" 13 | 14 | #define ITEM_HEADER_SIZE 0x08 15 | 16 | character **party= (character **)0x5b0; 17 | char *tempItemDesc= (char *)0x5d0; 18 | 19 | long int gPartyGold; 20 | long int gPartyExperience; 21 | 22 | item tempItem; 23 | 24 | item *inventoryItemForID(itemT anItemID) { 25 | static byte i; 26 | himemPtr ext; 27 | for (i= 0; i < 255; i++) { 28 | ext= itemBase + ITEM_HEADER_SIZE + (sizeof(item) * i); 29 | lcopy(ext, (long)&tempItem, sizeof(item)); 30 | if (tempItem.id == anItemID) { 31 | return &tempItem; 32 | } 33 | } 34 | return NULL; 35 | } 36 | 37 | char *rawNameOfInventoryItem(item *anItem) { 38 | himemPtr ext; 39 | ext= itemBase + anItem->namePtr; 40 | lcopy(ext, (long)tempItemDesc, 32); 41 | return tempItemDesc; 42 | } 43 | 44 | char *nameOfInventoryItem(item *anItem) { 45 | char *rawName; 46 | rawName= rawNameOfInventoryItem(anItem); 47 | 48 | if (anItem->type == it_scroll) { 49 | sprintf(drbuf, "%s %d", rawName, anItem->val1); 50 | return drbuf; 51 | } 52 | if (anItem->val3 > 0) { 53 | sprintf(drbuf, "%s +%d", rawName, anItem->val3); 54 | return drbuf; 55 | } 56 | return rawName; 57 | } 58 | 59 | byte hasInventoryItem(character *aCharacter, itemT anItemID) { 60 | static byte i; 61 | for (i= 0; i < INV_SIZE; ++i) { 62 | if (aCharacter->inventory[i] == anItemID) { 63 | return true; 64 | } 65 | } 66 | return false; 67 | } 68 | 69 | byte nextFreeInventorySlot(character *aCharacter) { 70 | static byte i; 71 | for (i= 0; i < INV_SIZE; ++i) { 72 | if (!aCharacter->inventory[i]) { 73 | return i; 74 | } 75 | } 76 | return 0xff; 77 | } 78 | 79 | byte addInventoryItem(byte anItemID, character *aCharacter) { 80 | static byte i; 81 | i= nextFreeInventorySlot(aCharacter); 82 | if (i != 0xff) { 83 | aCharacter->inventory[i]= anItemID; 84 | return anItemID; 85 | } 86 | return 0; 87 | } 88 | 89 | void debugAddItem(character *aCharacter) { 90 | byte itemID; 91 | cg_puts("ADD:"); 92 | fgets(drbuf, 3, stdin); 93 | itemID= atoi(drbuf); 94 | cg_printf("added %d", addInventoryItem(itemID, aCharacter)); 95 | } 96 | 97 | signed char bonusValueForAttribute(attrT a) { return -3 + (a / 3); } 98 | 99 | byte getNumberOfAttacks(character *aCharacter) { 100 | // TODO 101 | return 1; 102 | } 103 | 104 | int getHitDiceForCharacter(character *aCharacter) { 105 | item *weapon; 106 | if (aCharacter->weapon) { 107 | weapon= getWeapon(aCharacter); 108 | return weapon->val2; 109 | } 110 | return 3; 111 | } 112 | 113 | int getArmorClassForCharacter(character *aCharacter) { 114 | int retAC= 10; 115 | item *armor; 116 | item *shield; 117 | 118 | retAC-= bonusValueForAttribute(aCharacter->attributes[aDEX]); 119 | armor= getArmor(aCharacter); 120 | shield= getShield(aCharacter); 121 | if (armor) { 122 | retAC-= armor->val1; 123 | } 124 | if (shield) { 125 | retAC-= shield->val1; 126 | } 127 | // todo: test for rings etc. 128 | return retAC; 129 | } 130 | 131 | char *bonusStrForAttribute(attrT a) { 132 | static char ret[6]; 133 | signed char b= bonusValueForAttribute(a); 134 | strcpy(ret, " "); 135 | if (b > 0) { 136 | sprintf(ret, "(+%d)", b); 137 | } else if (b < 0) { 138 | sprintf(ret, "(%d)", b); 139 | } 140 | return ret; 141 | } 142 | 143 | byte partyMemberCount(void) { 144 | static byte i; 145 | byte n= 0; 146 | for (i= 0; i < PARTYSIZE; ++i) { 147 | if (party[i]) { 148 | ++n; 149 | } 150 | } 151 | return n; 152 | } 153 | 154 | void showCurrentParty(byte startX, byte startY, byte small) { 155 | static byte i, x, y; 156 | character *c; 157 | 158 | y= startY; 159 | 160 | if (small) { 161 | x= 18; 162 | cg_textcolor(COLOR_YELLOW); 163 | cg_putsxy(18, 1, "#"); 164 | cg_putsxy(20, 1, "Name"); 165 | cg_putsxy(33, 1, "Status"); 166 | cg_textcolor(COLOR_GRAY2); 167 | cg_hlinexy(17, 2, 38, 1); 168 | cg_hlinexy(17, 3 + partyMemberCount(), 38, 1); 169 | } else { 170 | x= startX; 171 | cg_textcolor(COLOR_YELLOW); 172 | cg_putsxy(17, y, "MP"); 173 | cg_putsxy(25, y, "HP"); 174 | cg_putsxy(3, y, "Name"); 175 | cg_putsxy(1, y, "#"); 176 | cg_putsxy(33, y, "Status"); 177 | } 178 | 179 | for (i= 0; i < PARTYSIZE; i++) { 180 | if (party[i]) { 181 | c= party[i]; 182 | ++y; 183 | cg_gotoxy(x, y); 184 | if (c->status == asleep) { 185 | cg_textcolor(COLOR_BLUE); 186 | } 187 | if (c->aHP >= 1) { 188 | cg_textcolor(COLOR_GREEN); 189 | } else { 190 | cg_textcolor(COLOR_RED); 191 | } 192 | if (small) { 193 | *drbuf= 0; 194 | strncat(drbuf, c->name, 12); 195 | cg_printf("%d %s", i + 1, drbuf); 196 | } else { 197 | cg_printf("%d %s", i + 1, c->name); 198 | } 199 | if (!small) { 200 | cg_gotoxy(17, y); 201 | cg_printf("%d/%d", c->aMP, c->aMaxMP); 202 | cg_gotoxy(25, y); 203 | cg_printf("%d/%d", c->aHP, c->aMaxHP); 204 | } 205 | cg_putsxy(35, y, gStateDesc[c->status]); 206 | } 207 | } 208 | } 209 | 210 | void useSpecial(item *anItem) { 211 | if (anItem->id == 0) { 212 | cg_displayErrorStatus("Curious. Nothing happens"); 213 | } 214 | } 215 | 216 | void more(char *filename) { 217 | FILE *infile; 218 | int line= 0; 219 | cg_clrscr(); 220 | infile= fopen(filename, "r"); 221 | while (!feof(infile)) { 222 | fgets(drbuf, DRBUFSIZE, infile); 223 | ++line; 224 | fputs(drbuf, stdout); 225 | if (line == gScreenRows - 2) { 226 | cg_gotoxy(28, gScreenRows - 1); 227 | cg_puts("-- more --"); 228 | cg_cursor(1); 229 | cg_getkey(); 230 | cg_cursor(0); 231 | line= 0; 232 | cg_clrscr(); 233 | } 234 | } 235 | fclose(infile); 236 | cg_gotoxy(28, gScreenRows - 1); 237 | cg_puts("-- key --"); 238 | cg_getkey(); 239 | } 240 | 241 | void useScroll(item *anItem) { 242 | static byte num; 243 | num= anItem->val1; 244 | sprintf(drbuf, "fmsg%02d", num); 245 | more(drbuf); 246 | } 247 | 248 | item *getEquippedItem(character *ic, byte itemIdxChar, byte *equipSlot) { 249 | *equipSlot= itemIdxChar - 'a'; 250 | switch (*equipSlot) { 251 | case 0: 252 | return inventoryItemForID(ic->weapon); 253 | break; 254 | 255 | case 1: 256 | return inventoryItemForID(ic->armor); 257 | break; 258 | 259 | case 2: 260 | return inventoryItemForID(ic->shield); 261 | break; 262 | 263 | default: 264 | break; 265 | } 266 | return NULL; 267 | } 268 | 269 | item *whichItem(character *ic, byte *inventorySlot, byte *equipSlot) { 270 | item *anItem; 271 | static byte itemIdxChar; 272 | 273 | *equipSlot= 255; 274 | *inventorySlot= 255; 275 | 276 | cg_puts("which item (A-O)? "); 277 | cg_cursor(1); 278 | do { 279 | itemIdxChar= cg_getkey(); 280 | } while (itemIdxChar < 'a' || itemIdxChar > 'o'); 281 | 282 | cg_cursor(0); 283 | if (itemIdxChar >= 'a' && itemIdxChar <= 'c') { 284 | anItem= getEquippedItem(ic, itemIdxChar, equipSlot); 285 | return anItem; 286 | } 287 | *inventorySlot= itemIdxChar - 'd'; 288 | anItem= inventoryItemForID(ic->inventory[*inventorySlot]); 289 | return anItem; 290 | } 291 | 292 | void giveItem(character *ic) { 293 | character *destCharacter; 294 | item *anItem; 295 | static byte inventorySlot; 296 | static byte equipSlot; 297 | static byte memberIdx; 298 | 299 | cg_clearBottomLine(); 300 | cg_gotoxy(0, gScreenRows - 1); 301 | cg_puts("give "); 302 | anItem= whichItem(ic, &inventorySlot, &equipSlot); 303 | if (inventorySlot == 255) { 304 | cg_displayErrorStatus("unequip item first!"); 305 | return; 306 | } 307 | cg_clearBottomLine(); 308 | cg_block_raw(1, 15, 38, 24, 32, 5); 309 | showCurrentParty(1, 16, 0); 310 | cg_gotoxy(0, gScreenRows - 1); 311 | cg_puts("to party member #"); 312 | cg_cursor(1); 313 | memberIdx= cg_getkey() - '1'; 314 | cg_cursor(0); 315 | if (memberIdx > 6 || party[memberIdx] == NULL) { 316 | cg_displayErrorStatus("...to whom?!"); 317 | return; 318 | } 319 | destCharacter= party[memberIdx]; 320 | if (!addInventoryItem(ic->inventory[inventorySlot], destCharacter)) { 321 | cg_displayErrorStatus("no space in inventory!"); 322 | return; 323 | } 324 | ic->inventory[inventorySlot]= 0; 325 | return; 326 | } 327 | 328 | void removeItem(character *ic) { 329 | item *anItem; 330 | static byte equipmentSlot; 331 | static byte inventorySlot; 332 | cg_clearBottomLine(); 333 | cg_gotoxy(0, gScreenRows - 1); 334 | cg_puts("unequip "); 335 | anItem= whichItem(ic, &inventorySlot, &equipmentSlot); 336 | cg_clearBottomLine(); 337 | cg_gotoxy(0, 23); 338 | if (equipmentSlot == 255) { 339 | cg_displayErrorStatus("not equipped item!"); 340 | return; 341 | } 342 | addInventoryItem(anItem->id, ic); 343 | switch (equipmentSlot) { 344 | case 0: 345 | ic->weapon= 0; 346 | break; 347 | 348 | case 1: 349 | ic->armor= 0; 350 | break; 351 | 352 | case 2: 353 | ic->shield= 0; 354 | 355 | default: 356 | break; 357 | } 358 | } 359 | 360 | void equipItem(item *anItem, byte inventorySlot, character *ic) { 361 | cg_clearLower(2); 362 | cg_gotoxy(0, 23); 363 | switch (anItem->type) { 364 | case it_weapon: 365 | case it_missile: 366 | if (ic->weapon != 0) { 367 | addInventoryItem(ic->weapon, ic); 368 | } 369 | ic->weapon= ic->inventory[inventorySlot]; 370 | ic->inventory[inventorySlot]= 0; 371 | break; 372 | case it_armor: 373 | if (ic->armor != 0) { 374 | addInventoryItem(ic->armor, ic); 375 | } 376 | ic->armor= ic->inventory[inventorySlot]; 377 | ic->inventory[inventorySlot]= 0; 378 | break; 379 | case it_shield: 380 | if (ic->shield != 0) { 381 | addInventoryItem(ic->armor, ic); 382 | } 383 | ic->shield= ic->inventory[inventorySlot]; 384 | ic->inventory[inventorySlot]= 0; 385 | break; 386 | 387 | default: 388 | break; 389 | } 390 | } 391 | 392 | void useOrEquipItem(character *ic) { 393 | item *anItem; 394 | static byte equipmentSlot; 395 | static byte inventorySlot; 396 | cg_pushWin(); 397 | cg_clearLower(1); 398 | cg_gotoxy(0, gScreenRows - 1); 399 | cg_puts("use "); 400 | anItem= whichItem(ic, &inventorySlot, &equipmentSlot); 401 | 402 | switch (anItem->type) { 403 | 404 | case it_special: 405 | useSpecial(anItem); 406 | break; 407 | 408 | case it_scroll: 409 | useScroll(anItem); 410 | break; 411 | 412 | case it_armor: 413 | case it_missile: 414 | case it_shield: 415 | case it_weapon: 416 | if (equipmentSlot != 255) { 417 | cg_displayErrorStatus("Already equipped item!"); 418 | } else { 419 | equipItem(anItem, inventorySlot, ic); 420 | } 421 | break; 422 | 423 | default: 424 | break; 425 | } 426 | cg_popWin(); 427 | return; 428 | } 429 | 430 | void displayInventoryAtRow(character *ic, byte row, char firstChar) { 431 | static byte i; 432 | for (i= 0; i < INV_SIZE; i++) { 433 | cg_gotoxy(20 * (i / (INV_SIZE / 2)), row + (i % (INV_SIZE / 2))); 434 | cg_revers(1); 435 | cg_putc(firstChar + i); 436 | cg_revers(0); 437 | cg_putc(32); 438 | cg_puts(nameOfInventoryItemWithID(ic->inventory[i])); 439 | } 440 | } 441 | 442 | void inspectCharacter(byte idx) { 443 | character *ic; 444 | static byte i; 445 | static byte quitInspect; 446 | static byte cmd; 447 | static byte spellLine; 448 | 449 | char *inspectMenu[]= {"Use/equip", "Unequip", "Give", "Exit", NULL}; 450 | 451 | if (party[idx] == NULL) { 452 | return; 453 | } 454 | 455 | quitInspect= false; 456 | 457 | while (!quitInspect) { 458 | 459 | cg_borders(0); 460 | cg_setwin(1, 1, 38, 24); 461 | 462 | spellLine= 0; 463 | ic= party[idx]; 464 | cg_clrscr(); 465 | cg_hlinexy(1, 2, 38, 1); 466 | cg_hlinexy(1, 14, 38, 1); 467 | 468 | cg_textcolor(5); 469 | 470 | cg_revers(1); 471 | cg_puts(ic->name); 472 | cg_revers(0); 473 | cg_printf(" (%s, %s)\n", gRaces[ic->aRace], gClasses[ic->aClass]); 474 | for (i= 0; i < NUM_ATTRS; i++) { 475 | cg_putsxy(0, i + 2, gAttributesS[i]); 476 | cg_putsxy(3, i + 2, ":"); 477 | cg_gotoxy(5, i + 2); 478 | cg_printf("%2d %s", ic->attributes[i], 479 | bonusStrForAttribute(ic->attributes[i])); 480 | } 481 | cg_gotoxy(0, i + 3); 482 | cg_printf(" HP: %d/%d\n", ic->aHP, ic->aMaxHP); 483 | cg_printf(" MP: %d/%d\n", ic->aMP, ic->aMaxMP); 484 | cg_printf(" AC: %d", getArmorClassForCharacter(ic)); 485 | cg_gotoxy(24, 2); 486 | cg_puts("Spells:"); 487 | cg_gotoxy(24, 3); 488 | for (i= 1; i < 64; ++i) { 489 | if (hasSpell(ic, i)) { 490 | cg_printf("%2d ", i); 491 | } 492 | if (cg_wherex() >= 38) { 493 | ++spellLine; 494 | cg_gotoxy(24, 3 + spellLine); 495 | } 496 | } 497 | cg_gotoxy(13, 2); 498 | cg_printf("Age: %d", ic->age); 499 | cg_gotoxy(13, 3); 500 | cg_printf("Lvl: %d", ic->level); 501 | cg_gotoxy(13, 4); 502 | cg_printf(" XP: %d", ic->xp); 503 | cg_gotoxy(13, 5); 504 | cg_printf("Cns: %d", ic->gold); 505 | cg_gotoxy(13, 9); 506 | cg_revers(1); 507 | cg_putc('A'); 508 | cg_revers(0); 509 | cg_printf(" Weapon: %s", nameOfInventoryItemWithID(ic->weapon)); 510 | cg_gotoxy(13, 10); 511 | cg_revers(1); 512 | cg_putc('B'); 513 | cg_revers(0); 514 | cg_printf(" Armor: %s", nameOfInventoryItemWithID(ic->armor)); 515 | cg_gotoxy(13, 11); 516 | cg_revers(1); 517 | cg_putc('C'); 518 | cg_revers(0); 519 | cg_printf(" Shield: %s", nameOfInventoryItemWithID(ic->shield)); 520 | cg_gotoxy(0, 14); 521 | cg_puts("Inventory:"); 522 | displayInventoryAtRow(ic, 16, 'D'); 523 | cg_resetwin(); 524 | 525 | cg_textcolor(COLOR_PURPLE); 526 | cmd= runBottomMenuN(inspectMenu); 527 | 528 | if (cmd >= 100) { 529 | i= cmd - 100; 530 | if (party[i] != NULL) { 531 | idx= i; 532 | } 533 | } 534 | 535 | switch (cmd) { 536 | case 3: 537 | quitInspect= true; 538 | break; 539 | 540 | case 0: 541 | useOrEquipItem(ic); 542 | break; 543 | 544 | case 1: 545 | removeItem(ic); 546 | break; 547 | 548 | case 2: 549 | giveItem(ic); 550 | break; 551 | 552 | #ifdef DEBUG 553 | case 'D': 554 | debugAddItem(ic); 555 | break; 556 | #endif 557 | default: 558 | break; 559 | } 560 | 561 | } // while !quitInspect 562 | } 563 | 564 | byte loadParty(void) { 565 | FILE *infile; 566 | static byte i, count; 567 | character *newChar; 568 | 569 | infile= fopen("pdata", "r"); 570 | if (!infile) { 571 | return false; 572 | } 573 | 574 | count= fgetc(infile); 575 | 576 | for (i= 0; i < count; ++i) { 577 | newChar= malloc(sizeof(character)); 578 | fread(newChar, sizeof(character), 1, infile); 579 | party[i]= newChar; 580 | } 581 | 582 | fclose(infile); 583 | return true; 584 | } 585 | --------------------------------------------------------------------------------