diff --git a/Makefile b/Makefile index ca04c65..17d6a28 100644 --- a/Makefile +++ b/Makefile @@ -1,114 +1,113 @@ +include config.mk -SDL_FLAGS = `sdl-config --cflags` `sdl-config --libs` -LIBTCODDIR=src/libtcod-1.5.2 -CFLAGS=-Isrc/brogue -Isrc/platform -Wall -Wno-parentheses ${DEFINES} -RELEASENAME=brogue-1.7.4 -LASTTARGET := $(shell ./brogue --target) -CC ?= gcc - -ifeq (${LASTTARGET},both) -all : both -else ifeq (${LASTTARGET},curses) -all : curses -else ifeq (${LASTTARGET},tcod) -all : tcod +cflags := -Isrc/brogue -Isrc/platform -std=c99 \ + -Wall -Wpedantic -Werror=implicit -Wno-parentheses -Wno-unused-result \ + -Wformat -Werror=format-security -Wformat-overflow=0 +libs := -lm +cppflags := -DDATADIR=$(DATADIR) + +sources := $(wildcard src/brogue/*.c) $(addprefix src/platform/,main.c platformdependent.c) + +ifeq ($(RELEASE),YES) + extra_version := else -all : both + extra_version := $(shell bash tools/git-extra-version) +endif +cppflags += -DBROGUE_EXTRA_VERSION='"$(extra_version)"' + +ifeq ($(TERMINAL),YES) + sources += $(addprefix src/platform/,curses-platform.c term.c) + cppflags += -DBROGUE_CURSES + libs += -lncurses +endif + +ifeq ($(GRAPHICS),YES) + sources += $(addprefix src/platform/,sdl2-platform.c tiles.c) + cflags += $(shell $(SDL_CONFIG) --cflags) + cppflags += -DBROGUE_SDL + libs += $(shell $(SDL_CONFIG) --libs) -lSDL2_image +endif + +ifeq ($(WEBBROGUE),YES) + sources += $(addprefix src/platform/,web-platform.c) + cppflags += -DBROGUE_WEB +endif + +ifeq ($(MAC_APP),YES) + cppflags += -DSDL_PATHS endif -%.o : %.c Makefile src/brogue/Rogue.h src/brogue/IncludeGlobals.h - $(CC) $(CFLAGS) -g -o $@ -c $< - -BROGUEFILES=src/brogue/Architect.o \ - src/brogue/Bot.o \ - src/brogue/Combat.o \ - src/brogue/Dijkstra.o \ - src/brogue/Globals.o \ - src/brogue/IO.o \ - src/brogue/Items.o \ - src/brogue/Light.o \ - src/brogue/Monsters.o \ - src/brogue/Buttons.o \ - src/brogue/Movement.o \ - src/brogue/Recordings.o \ - src/brogue/RogueMain.o \ - src/brogue/Random.o \ - src/brogue/MainMenu.o \ - src/brogue/Grid.o \ - src/brogue/Time.o \ - src/platform/main.o \ - src/platform/platformdependent.o \ - src/platform/curses-platform.o \ - src/platform/tcod-platform.o \ - src/platform/term.o - -TCOD_DEF = -DBROGUE_TCOD -I$(LIBTCODDIR)/include -TCOD_DEP = ${LIBTCODDIR} -TCOD_LIB = -L. -L${LIBTCODDIR} ${SDL_FLAGS} -ltcod -Wl,-rpath,. - -CURSES_DEF = -DBROGUE_CURSES -CURSES_LIB = -lncurses -lm - -LIBRARIES += -llua - -tcod : DEPENDENCIES += ${TCOD_DEP} -tcod : DEFINES += ${TCOD_DEF} -tcod : LIBRARIES += ${TCOD_LIB} - -curses : DEFINES += ${CURSES_DEF} -curses : LIBRARIES += ${CURSES_LIB} - -both : DEPENDENCIES += ${TCOD_DEP} -both : DEFINES += ${TCOD_DEF} ${CURSES_DEF} -both : LIBRARIES += ${TCOD_LIB} ${CURSES_LIB} - -ifeq (${LASTTARGET},both) -both : bin/brogue -tcod : clean bin/brogue -curses : clean bin/brogue -else ifeq (${LASTTARGET},curses) -curses : bin/brogue -tcod : clean bin/brogue -both : clean bin/brogue -else ifeq (${LASTTARGET},tcod) -tcod : bin/brogue -curses : clean bin/brogue -both : clean bin/brogue +ifeq ($(DEBUG),YES) + cflags += -g -Og + cppflags += -DENABLE_PLAYBACK_SWITCH else -both : bin/brogue -curses : bin/brogue -tcod : bin/brogue + cflags += -O2 endif -.PHONY : clean both curses tcod tar - -bin/brogue : ${DEPENDENCIES} ${BROGUEFILES} - $(CC) -O2 -march=i586 -o bin/brogue ${BROGUEFILES} ${LIBRARIES} -Wl,-rpath,. - -clean : - rm -f src/brogue/*.o src/platform/*.o bin/brogue - -${LIBTCODDIR} : - src/get-libtcod.sh - -tar : both - rm -f ${RELEASENAME}.tar.gz - tar --transform 's,^,${RELEASENAME}/,' -czf ${RELEASENAME}.tar.gz \ - Makefile \ - brogue \ - $(wildcard *.sh) \ - $(wildcard *.rtf) \ - readme \ - $(wildcard *.txt) \ - bin/brogue \ - bin/keymap \ - bin/icon.bmp \ - bin/brogue-icon.png \ - $(wildcard bin/fonts/*.png) \ - $(wildcard bin/*.so) \ - $(wildcard src/*.sh) \ - $(wildcard src/brogue/*.c) \ - $(wildcard src/brogue/*.h) \ - $(wildcard src/platform/*.c) \ - $(wildcard src/platform/*.h) +objects := $(sources:.c=.o) + +.PHONY: clean + +%.o: %.c src/brogue/Rogue.h src/brogue/IncludeGlobals.h + $(CC) $(cppflags) $(CPPFLAGS) $(cflags) $(CFLAGS) -c $< -o $@ + +bin/brogue: $(objects) + $(CC) $(cflags) $(CFLAGS) $(LDFLAGS) -o $@ $^ $(libs) $(LDLIBS) + +windows/icon.o: windows/icon.rc + windres $< $@ + +bin/brogue.exe: $(objects) windows/icon.o + $(CC) $(cflags) $(CFLAGS) $(LDFLAGS) -o $@ $^ $(libs) $(LDLIBS) + mt -manifest windows/brogue.exe.manifest '-outputresource:bin/brogue.exe;1' + +clean: + $(RM) src/brogue/*.o src/platform/*.o windows/icon.o bin/brogue{,.exe} + + +# Release archives + +common_bin := bin/assets bin/keymap.txt + +define make_release_base + mkdir $@ + cp README.md $@/README.txt + cp CHANGELOG.md $@/CHANGELOG.txt + cp LICENSE.txt $@ +endef + +# Flatten bin/ in the Windows archive +BrogueCE-windows: bin/brogue.exe + $(make_release_base) + cp -r $(common_bin) bin/{brogue.exe,brogue-cmd.bat} $@ + +BrogueCE-macos: Brogue.app + $(make_release_base) + cp -r Brogue.app $@/"Brogue CE.app" + +BrogueCE-linux: bin/brogue + $(make_release_base) + cp brogue $@ + cp -r --parents $(common_bin) bin/brogue $@ + cp linux/make-link-for-desktop.sh $@ + + +# macOS app bundle + +# $* is the matched % +icon_%.png: bin/assets/icon.png + convert $< -resize $* $@ + +macos/Brogue.icns: icon_32.png icon_128.png icon_256.png icon_512.png + png2icns $@ $^ + $(RM) $^ + +Brogue.app: bin/brogue + mkdir -p $@/Contents/{MacOS,Resources} + cp macos/Info.plist $@/Contents + cp bin/brogue $@/Contents/MacOS + cp -r macos/Brogue.icns bin/assets $@/Contents/Resources +macos/sdl2.rb: + curl -L 'https://raw.githubusercontent.com/Homebrew/homebrew-core/master/Formula/sdl2.rb' >$@ + patch $@ macos/sdl2-deployment-target.patch diff --git a/bin/definitions.lua b/bin/definitions.lua index 25965a0..3586787 100644 --- a/bin/definitions.lua +++ b/bin/definitions.lua @@ -30,11 +30,11 @@ ITEM_DETECTED = Fl(12) -- magic-detected item on cell CLAIRVOYANT_VISIBLE = Fl(13) CLAIRVOYANT_DARKENED = Fl(15) -- magical blindness from a cursed ring of clairvoyance CAUGHT_FIRE_THIS_TURN = Fl(16) -- so that fire does not spread asymmetrically -KNOWN_TO_BE_TRAP_FREE = Fl(19) -- keep track of where the player has stepped as he knows no traps are there +KNOWN_TO_BE_TRAP_FREE = Fl(19) -- keep track of where the player has stepped or watched monsters step as he knows no traps are there TELEPATHIC_VISIBLE = Fl(29) -- potions of telepathy let you see through other creatures' eyes -- ~ PERMANENT_TILE_FLAGS = (DISCOVERED | MAGIC_MAPPED | ITEM_DETECTED | HAS_ITEM | HAS_DORMANT_MONSTER - -- ~ | HAS_UP_STAIRS | HAS_DOWN_STAIRS | PRESSURE_PLATE_DEPRESSED + -- ~ | HAS_MONSTER | HAS_STAIRS | SEARCHED_FROM_HERE | PRESSURE_PLATE_DEPRESSED -- ~ | STABLE_MEMORY | KNOWN_TO_BE_TRAP_FREE | IN_LOOP -- ~ | IS_CHOKEPOINT | IS_GATE_SITE | IS_IN_MACHINE | IMPREGNABLE) @@ -122,25 +122,28 @@ MONST_NEVER_MUTATED = (MONST_INVISIBLE | MONST_INANIMATE | MONST_IMM -- monster ability flags MA_HIT_HALLUCINATE = Fl(0) -- monster can hit to cause hallucinations MA_HIT_STEAL_FLEE = Fl(1) -- monster can steal an item and then run away -MA_ENTER_SUMMONS = Fl(2) -- monster will "become" its summoned leader, reappearing when that leader is defeated -MA_HIT_DEGRADE_ARMOR = Fl(3) -- monster damages armor -MA_CAST_SUMMON = Fl(4) -- requires that there be one or more summon hordes with this monster type as the leader -MA_SEIZES = Fl(5) -- monster seizes enemies before attacking -MA_POISONS = Fl(6) -- monster's damage is dealt in the form of poison -MA_DF_ON_DEATH = Fl(7) -- monster spawns its DF when it dies -MA_CLONE_SELF_ON_DEFEND = Fl(8) -- monster splits in two when struck -MA_KAMIKAZE = Fl(9) -- monster dies instead of attacking -MA_TRANSFERENCE = Fl(10) -- monster recovers 40 or 90% of the damage that it inflicts as health -MA_CAUSES_WEAKNESS = Fl(11) -- monster attacks cause weakness status in target -MA_ATTACKS_PENETRATE = Fl(12) -- monster attacks all adjacent enemies, like an axe -MA_ATTACKS_ALL_ADJACENT = Fl(13) -- monster attacks penetrate one layer of enemies, like a spear -MA_ATTACKS_EXTEND = Fl(14) -- monster attacks from a distance in a cardinal direction, like a whip -MA_AVOID_CORRIDORS = Fl(15) -- monster will avoid corridors when hunting - -SPECIAL_HIT = (MA_HIT_HALLUCINATE | MA_HIT_STEAL_FLEE | MA_HIT_DEGRADE_ARMOR | MA_POISONS | MA_TRANSFERENCE | MA_CAUSES_WEAKNESS) +MA_HIT_BURN = Fl(2) -- monster can hit to set you on fire +MA_ENTER_SUMMONS = Fl(3) -- monster will "become" its summoned leader, reappearing when that leader is defeated +MA_HIT_DEGRADE_ARMOR = Fl(4) -- monster damages armor +MA_CAST_SUMMON = Fl(5) -- requires that there be one or more summon hordes with this monster type as the leader +MA_SEIZES = Fl(6) -- monster seizes enemies before attacking +MA_POISONS = Fl(7) -- monster's damage is dealt in the form of poison +MA_DF_ON_DEATH = Fl(8) -- monster spawns its DF when it dies +MA_CLONE_SELF_ON_DEFEND = Fl(9) -- monster splits in two when struck +MA_KAMIKAZE = Fl(10) -- monster dies instead of attacking +MA_TRANSFERENCE = Fl(11) -- monster recovers 40 or 90% of the damage that it inflicts as health +MA_CAUSES_WEAKNESS = Fl(12) -- monster attacks cause weakness status in target +MA_ATTACKS_PENETRATE = Fl(13) -- monster attacks all adjacent enemies, like an axe +MA_ATTACKS_ALL_ADJACENT = Fl(14) -- monster attacks penetrate one layer of enemies, like a spear +MA_ATTACKS_EXTEND = Fl(15) -- monster attacks from a distance in a cardinal direction, like a whip +MA_ATTACKS_STAGGER = Fl(16) -- monster attacks will push the player backward by one space if there is room +MA_AVOID_CORRIDORS = Fl(17) -- monster will avoid corridors when hunting + +SPECIAL_HIT = (MA_HIT_HALLUCINATE | MA_HIT_STEAL_FLEE | MA_HIT_DEGRADE_ARMOR | MA_POISONS + | MA_TRANSFERENCE | MA_CAUSES_WEAKNESS | MA_HIT_BURN | MA_ATTACKS_STAGGER) LEARNABLE_ABILITIES = (MA_TRANSFERENCE | MA_CAUSES_WEAKNESS) -MA_NON_NEGATABLE_ABILITIES = (MA_ATTACKS_PENETRATE | MA_ATTACKS_ALL_ADJACENT) +MA_NON_NEGATABLE_ABILITIES = (MA_ATTACKS_PENETRATE | MA_ATTACKS_ALL_ADJACENT | MA_ATTACKS_EXTEND | MA_ATTACKS_STAGGER) MA_NEVER_VORPAL_ENEMY = (MA_KAMIKAZE) MA_NEVER_MUTATED = (MA_KAMIKAZE) @@ -151,9 +154,9 @@ MB_CAPTIVE = Fl(8) -- monster is all tied up MB_SEIZED = Fl(9) -- monster is being held MB_SEIZING = Fl(10) -- monster is holding another creature immobile MB_SUBMERGED = Fl(11) -- monster is currently submerged and hence invisible until it attacks -MB_ABSORBING = Fl(15) -- currently learning a skill by absorbing an enemy corpse -MB_HAS_SOUL = Fl(21) -- slaying the monster will count toward weapon auto-ID -MB_ALREADY_SEEN = Fl(22) -- seeing this monster won't interrupt exploration +MB_ABSORBING = Fl(16) -- currently learning a skill by absorbing an enemy corpse +MB_HAS_SOUL = Fl(22) -- slaying the monster will count toward weapon auto-ID +MB_ALREADY_SEEN = Fl(23) -- seeing this monster won't interrupt exploration -- monster states @@ -167,6 +170,7 @@ MONSTER_ALLY = nexti() -- creature status effect indices i = 0 -- start from 1 as these are table indices +STATUS_DONNING = nexti() STATUS_WEAKENED = nexti() STATUS_TELEPATHIC = nexti() STATUS_HALLUCINATING = nexti() @@ -184,6 +188,7 @@ STATUS_IMMUNE_TO_FIRE = nexti() STATUS_EXPLOSION_IMMUNITY = nexti() STATUS_NUTRITION = nexti() STATUS_ENTERS_LEVEL_IN = nexti() +STATUS_ENRAGED = nexti() -- temporarily ignores normal MA_AVOID_CORRIDORS behavior STATUS_MAGICAL_FEAR = nexti() STATUS_ENTRANCED = nexti() STATUS_DARKNESS = nexti() @@ -204,7 +209,7 @@ ITEM_FLAMMABLE = Fl(10) ITEM_MAGIC_DETECTED = Fl(11) ITEM_IS_KEY = Fl(13) -ITEM_ATTACKS_HIT_SLOWLY = Fl(14) -- mace, hammer +ITEM_ATTACKS_STAGGER = Fl(14) -- mace, hammer ITEM_ATTACKS_EXTEND = Fl(15) -- whip ITEM_ATTACKS_QUICKLY = Fl(16) -- rapier ITEM_ATTACKS_PENETRATE = Fl(17) -- spear, pike @@ -432,7 +437,6 @@ ACID_JELLY = nexti() CENTAUR = nexti() UNDERWORM = nexti() SENTINEL = nexti() -ACID_TURRET = nexti() DART_TURRET = nexti() KRAKEN = nexti() LICH = nexti() @@ -578,6 +582,10 @@ STONE_BRIDGE = nexti() MACHINE_FLOOD_WATER_DORMANT = nexti() MACHINE_FLOOD_WATER_SPREADING = nexti() MACHINE_MUD_DORMANT = nexti() +ICE_DEEP = nexti() +ICE_DEEP_MELT = nexti() +ICE_SHALLOW = nexti() +ICE_SHALLOW_MELT = nexti() HOLE = nexti() HOLE_GLOW = nexti() HOLE_EDGE = nexti() @@ -659,6 +667,11 @@ PIPE_INERT = nexti() RESURRECTION_ALTAR = nexti() RESURRECTION_ALTAR_INERT = nexti() MACHINE_TRIGGER_FLOOR_REPEATING = nexti() +SACRIFICE_ALTAR_DORMANT = nexti() +SACRIFICE_ALTAR = nexti() +SACRIFICE_LAVA = nexti() +SACRIFICE_CAGE_DORMANT = nexti() +DEMONIC_STATUE = nexti() STATUE_INERT_DOORWAY = nexti() STATUE_DORMANT_DOORWAY = nexti() CHASM_WITH_HIDDEN_BRIDGE = nexti() diff --git a/config.mk b/config.mk new file mode 100644 index 0000000..b33967b --- /dev/null +++ b/config.mk @@ -0,0 +1,22 @@ +# Where to look for game data files (found in 'bin'). Must be without trailing slashes! +DATADIR := . + +# Include terminal support. Requires ncurses +TERMINAL := NO + +# Include graphical support. Requires SDL2 and SDL2_image +GRAPHICS := YES +# Path to sdl2-config script +SDL_CONFIG := sdl2-config + +# Select web brogue mode. Requires POSIX system. +WEBBROGUE := NO + +# Enable debugging mode. See top of Rogue.h for features +DEBUG := NO + +# Declare this is a release build +RELEASE := NO + +# Configure the executable to run from a macOS .app bundle (only works in graphical mode) +MAC_APP := NO diff --git a/src/brogue/Architect.c b/src/brogue/Architect.c index dd6e6d6..182ba36 100644 --- a/src/brogue/Architect.c +++ b/src/brogue/Architect.c @@ -4,7 +4,7 @@ * * Created by Brian Walker on 1/10/09. * Copyright 2012. All rights reserved. - * + * * This file is part of Brogue. * * This program is free software: you can redistribute it and/or modify @@ -37,11 +37,11 @@ boolean checkLoopiness(short x, short y) { boolean inString; short newX, newY, dir, sdir; short numStrings, maxStringLength, currentStringLength; - + if (!(pmap[x][y].flags & IN_LOOP)) { return false; } - + // find an unloopy neighbor to start on for (sdir = 0; sdir < DIRECTION_COUNT; sdir++) { newX = x + cDirs[sdir][0]; @@ -54,7 +54,7 @@ boolean checkLoopiness(short x, short y) { if (sdir == 8) { // no unloopy neighbors return false; // leave cell loopy } - + // starting on this unloopy neighbor, work clockwise and count up (a) the number of strings // of loopy neighbors, and (b) the length of the longest such string. numStrings = maxStringLength = currentStringLength = 0; @@ -84,7 +84,7 @@ boolean checkLoopiness(short x, short y) { } if (numStrings == 1 && maxStringLength <= 4) { pmap[x][y].flags &= ~IN_LOOP; - + for (dir = 0; dir < DIRECTION_COUNT; dir++) { newX = x + cDirs[dir][0]; newY = y + cDirs[dir][1]; @@ -103,7 +103,7 @@ void auditLoop(short x, short y, char grid[DCOLS][DROWS]) { if (coordinatesAreInMap(x, y) && !grid[x][y] && !(pmap[x][y].flags & IN_LOOP)) { - + grid[x][y] = true; for (dir = 0; dir < DIRECTION_COUNT; dir++) { newX = x + nbDirs[dir][0]; @@ -119,22 +119,22 @@ void auditLoop(short x, short y, char grid[DCOLS][DROWS]) { // Returns 10000 if the area included an area machine. short floodFillCount(char results[DCOLS][DROWS], char passMap[DCOLS][DROWS], short startX, short startY) { short dir, newX, newY, count; - + count = (passMap[startX][startY] == 2 ? 5000 : 1); - + if (pmap[startX][startY].flags & IS_IN_AREA_MACHINE) { count = 10000; } - + results[startX][startY] = true; - + for(dir=0; dir<4; dir++) { newX = startX + nbDirs[dir][0]; newY = startY + nbDirs[dir][1]; if (coordinatesAreInMap(newX, newY) && passMap[newX][newY] && !results[newX][newY]) { - + count += floodFillCount(results, passMap, newX, newY); } } @@ -150,9 +150,9 @@ short floodFillCount(char results[DCOLS][DROWS], char passMap[DCOLS][DROWS], sho // Five or more means there is a bug. short passableArcCount(short x, short y) { short arcCount, dir, oldX, oldY, newX, newY; - + brogueAssert(coordinatesAreInMap(x, y)); - + arcCount = 0; for (dir = 0; dir < DIRECTION_COUNT; dir++) { oldX = x + cDirs[(dir + 7) % 8][0]; @@ -173,15 +173,15 @@ void analyzeMap(boolean calculateChokeMap) { short i, j, i2, j2, dir, newX, newY, oldX, oldY, passableArcCount, cellCount; char grid[DCOLS][DROWS], passMap[DCOLS][DROWS]; boolean designationSurvives; - + // first find all of the loops rogue.staleLoopMap = false; - + for(i=0; i= 4) { // Now, on the chokemap, all of those flooded cells should take the lesser of their current value or this resultant number. for(i2=0; i2 0 - && grid[oppX][oppY] > 0) { // If the tile being inspected has floor on both sides, - + && grid[newX][newY] == 1 + && grid[oppX][oppY] == 1) { // If the tile being inspected has floor on both sides, + fillGrid(pathMap, 30000); pathMap[newX][newY] = 0; dijkstraScan(pathMap, costMap, false); @@ -358,7 +358,7 @@ void addLoops(short **grid, short minimumPathingDistance) { grid[x][y] = 2; // then turn the tile into a doorway. costMap[x][y] = 1; // (Cost map also needs updating.) if (D_INSPECT_LEVELGEN) { - plotCharWithColor(DOOR_CHAR, mapToWindowX(x), mapToWindowY(y), &black, &green); + plotCharWithColor(G_CLOSED_DOOR, mapToWindowX(x), mapToWindowY(y), &black, &green); } break; } @@ -367,7 +367,7 @@ void addLoops(short **grid, short minimumPathingDistance) { } } if (D_INSPECT_LEVELGEN) { - temporaryMessage("Added secondary connections:", true); + temporaryMessage("Added secondary connections:", REQUIRE_ACKNOWLEDGMENT); } freeGrid(pathMap); freeGrid(costMap); @@ -379,9 +379,9 @@ void addLoops(short **grid, short minimumPathingDistance) { boolean addTileToMachineInteriorAndIterate(char interior[DCOLS][DROWS], short startX, short startY) { short dir, newX, newY; boolean goodSoFar = true; - + interior[startX][startY] = true; - + for (dir = 0; dir < 4 && goodSoFar; dir++) { newX = startX + nbDirs[dir][0]; newY = startY + nbDirs[dir][1]; @@ -409,7 +409,7 @@ boolean addTileToMachineInteriorAndIterate(char interior[DCOLS][DROWS], short st void copyMap(pcell from[DCOLS][DROWS], pcell to[DCOLS][DROWS]) { short i, j; - + for(i=0; icategory == theItem->category && spawnedItems[i]->kind == theItem->kind) { - + return true; } } @@ -440,7 +440,7 @@ boolean blueprintQualifies(short i, unsigned long requiredMachineFlags) { || (blueprintCatalog[i].flags & BP_ADOPT_ITEM & ~requiredMachineFlags) // May NOT have BP_VESTIBULE unless that flag is required: || (blueprintCatalog[i].flags & BP_VESTIBULE & ~requiredMachineFlags)) { - + return false; } return true; @@ -448,7 +448,7 @@ boolean blueprintQualifies(short i, unsigned long requiredMachineFlags) { void abortItemsAndMonsters(item *spawnedItems[MACHINES_BUFFER_LENGTH], creature *spawnedMonsters[MACHINES_BUFFER_LENGTH]) { short i, j; - + for (i=0; i 1) { return false; } - + // No building along the perimeter of the level if it's prohibited. if ((featureFlags & MF_NOT_ON_LEVEL_PERIMETER) && (x == 0 || x == DCOLS - 1 || y == 0 || y == DROWS - 1)) { return false; } - + // The origin is a candidate if the feature is flagged to be built at the origin. // If it's a room, the origin (i.e. doorway) is otherwise NOT a candidate. if (featureFlags & MF_BUILD_AT_ORIGIN) { @@ -501,18 +501,18 @@ boolean cellIsFeatureCandidate(short x, short y, } else if ((bpFlags & BP_ROOM) && x == originX && y == originY) { return false; } - + // No building in another feature's personal space! if (occupied[x][y]) { - return false; + return false; } - + // Must be in the viewmap if the appropriate flag is set. if ((featureFlags & (MF_IN_VIEW_OF_ORIGIN | MF_IN_PASSABLE_VIEW_OF_ORIGIN)) && !viewMap[x][y]) { return false; } - + // Do a distance check if the feature requests it. if (cellHasTerrainFlag(x, y, T_OBSTRUCTS_PASSABILITY)) { // Distance is calculated for walls too. distance = 10000; @@ -522,14 +522,14 @@ boolean cellIsFeatureCandidate(short x, short y, if (coordinatesAreInMap(newX, newY) && !cellHasTerrainFlag(newX, newY, T_OBSTRUCTS_PASSABILITY) && distance > distanceMap[newX][newY] + 1) { - + distance = distanceMap[newX][newY] + 1; } } } else { distance = distanceMap[x][y]; } - + if (distance > distanceBound[1] // distance exceeds max || distance < distanceBound[0]) { // distance falls short of min return false; @@ -558,7 +558,7 @@ boolean cellIsFeatureCandidate(short x, short y, && (cellHasTerrainFlag(x, y, T_OBSTRUCTS_ITEMS | T_PATHING_BLOCKER) || (pmap[x][y].flags & (IS_CHOKEPOINT | IN_LOOP | IS_IN_MACHINE)))) { return false; } else { - return true; + return !(pmap[x][y].flags & IS_IN_MACHINE); } } else if (interior[x][y]) { return true; @@ -569,7 +569,7 @@ boolean cellIsFeatureCandidate(short x, short y, void addLocationToKey(item *theItem, short x, short y, boolean disposableHere) { short i; - + for (i=0; i < KEY_ID_MAXIMUM && (theItem->keyLoc[i].x || theItem->keyLoc[i].machine); i++); theItem->keyLoc[i].x = x; theItem->keyLoc[i].y = y; @@ -578,7 +578,7 @@ void addLocationToKey(item *theItem, short x, short y, boolean disposableHere) { void addMachineNumberToKey(item *theItem, short machineNumber, boolean disposableHere) { short i; - + for (i=0; i < KEY_ID_MAXIMUM && (theItem->keyLoc[i].x || theItem->keyLoc[i].machine); i++); theItem->keyLoc[i].machine = machineNumber; theItem->keyLoc[i].disposableHere = disposableHere; @@ -588,14 +588,14 @@ void expandMachineInterior(char interior[DCOLS][DROWS], short minimumInteriorNei boolean madeChange; short nbcount, newX, newY, i, j, layer; enum directions dir; - + do { madeChange = false; for(i=1; i 0) { pathingGrid = allocGrid(); costGrid = allocGrid(); for (n = 0; n < orphanCount; n++) { - + if (D_INSPECT_MACHINES) { dumpLevelToScreen(); copyGrid(pathingGrid, grid); findReplaceGrid(pathingGrid, -1, -1, 0); hiliteGrid(pathingGrid, &green, 50); - plotCharWithColor('X', mapToWindowX(orphanList[n][0]), mapToWindowY(orphanList[n][1]), &black, &orange); - temporaryMessage("Orphan detected:", true); + plotCharWithColor('X', mapToWindowX(orphanList[n].x), mapToWindowY(orphanList[n].y), &black, &orange); + temporaryMessage("Orphan detected:", REQUIRE_ACKNOWLEDGMENT); } - + for (i=0; i 0) { for (dir = 0; dir < 4; dir++) { newX = i + nbDirs[dir][0]; newY = j + nbDirs[dir][1]; - + if (coordinatesAreInMap(newX, newY) && pathingGrid[newX][newY] < pathingGrid[i][j]) { - + grid[i][j] = 1; i = newX; j = newY; @@ -797,14 +804,14 @@ void redesignInterior(char interior[DCOLS][DROWS], short originX, short originY, dumpLevelToScreen(); displayGrid(pathingGrid); plotCharWithColor('X', mapToWindowX(i), mapToWindowY(j), &black, &orange); - temporaryMessage("Orphan connecting:", true); + temporaryMessage("Orphan connecting:", REQUIRE_ACKNOWLEDGMENT); } } } freeGrid(pathingGrid); freeGrid(costGrid); } - + addLoops(grid, 10); for(i=0; i0); } - + // Find a location and map out the machine interior. if (blueprintCatalog[bp].flags & BP_ROOM) { // If it's a room machine, count up the gates of appropriate // choke size and remember where they are. The origin of the room will be the gate location. - zeroOutGrid(interior); - + zeroOutGrid(p->interior); + if (chooseLocation) { analyzeMap(true); // Make sure the chokeMap is up to date. totalFreq = 0; @@ -1044,20 +1065,19 @@ boolean buildAMachine(enum machineTypes bp, && !(pmap[i][j].flags & IS_IN_MACHINE) && chokeMap[i][j] >= blueprintCatalog[bp].roomSize[0] && chokeMap[i][j] <= blueprintCatalog[bp].roomSize[1]) { - + //DEBUG printf("\nDepth %i: Gate site qualified with interior size of %i.", rogue.depthLevel, chokeMap[i][j]); - gateCandidates[totalFreq][0] = i; - gateCandidates[totalFreq][1] = j; + p->gateCandidates[totalFreq] = (pos){ .x = i, .y = j }; totalFreq++; } } } - + if (totalFreq) { // Choose the gate. randIndex = rand_range(0, totalFreq - 1); - originX = gateCandidates[randIndex][0]; - originY = gateCandidates[randIndex][1]; + originX = p->gateCandidates[randIndex].x; + originY = p->gateCandidates[randIndex].y; } else { // If no suitable sites, abort. if (distanceMap) { @@ -1066,15 +1086,16 @@ boolean buildAMachine(enum machineTypes bp, if (D_MESSAGE_MACHINE_GENERATION) printf("\nDepth %i: Failed to build a machine; there was no eligible door candidate for the chosen room machine from blueprint %i.", rogue.depthLevel, bp); + free(p); return false; } } - + // Now map out the interior into interior[][]. // Start at the gate location and do a depth-first floodfill to grab all adjoining tiles with the // same or lower choke value, ignoring any tiles that are already part of a machine. // If we get false from this, try again. If we've tried too many times already, abort. - tryAgain = !addTileToMachineInteriorAndIterate(interior, originX, originY); + tryAgain = !addTileToMachineInteriorAndIterate(p->interior, originX, originY); } else if (blueprintCatalog[bp].flags & BP_VESTIBULE) { if (chooseLocation) { // Door machines must have locations passed in. We can't pick one ourselves. @@ -1084,15 +1105,17 @@ boolean buildAMachine(enum machineTypes bp, if (D_MESSAGE_MACHINE_GENERATION) printf("\nDepth %i: ERROR: Attempted to build a door machine from blueprint %i without a location being provided.", rogue.depthLevel, bp); + free(p); return false; } - if (!fillInteriorForVestibuleMachine(interior, bp, originX, originY)) { + if (!fillInteriorForVestibuleMachine(p->interior, bp, originX, originY)) { if (distanceMap) { freeGrid(distanceMap); } if (D_MESSAGE_MACHINE_GENERATION) printf("\nDepth %i: Failed to build a door machine from blueprint %i; not enough room.", rogue.depthLevel, bp); + free(p); return false; } } else { @@ -1101,17 +1124,17 @@ boolean buildAMachine(enum machineTypes bp, // expand it along a pathing map by one space in all directions until the size reaches // the chosen size, and then make sure the resulting space qualifies. // If not, try again. If we've tried too many times already, abort. - + locationFailsafe = 10; do { - zeroOutGrid(interior); + zeroOutGrid(p->interior); tryAgain = false; - + if (chooseLocation) { // Pick a random origin location. randomMatchingLocation(&originX, &originY, FLOOR, NOTHING, -1); } - + if (!distanceMap) { distanceMap = allocGrid(); } @@ -1119,20 +1142,20 @@ boolean buildAMachine(enum machineTypes bp, calculateDistances(distanceMap, originX, originY, T_PATHING_BLOCKER, NULL, true, false); qualifyingTileCount = 0; // Keeps track of how many interior cells we've added. totalFreq = rand_range(blueprintCatalog[bp].roomSize[0], blueprintCatalog[bp].roomSize[1]); // Keeps track of the goal size. - - fillSequentialList(sCols, DCOLS); - shuffleList(sCols, DCOLS); - fillSequentialList(sRows, DROWS); - shuffleList(sRows, DROWS); - + + fillSequentialList(p->sCols, DCOLS); + shuffleList(p->sCols, DCOLS); + fillSequentialList(p->sRows, DROWS); + shuffleList(p->sRows, DROWS); + for (k=0; k<1000 && qualifyingTileCount < totalFreq; k++) { for(i=0; isCols[i]][p->sRows[j]] == k) { + p->interior[p->sCols[i]][p->sRows[j]] = true; qualifyingTileCount++; - - if (pmap[sCols[i]][sRows[j]].flags & (HAS_ITEM | HAS_MONSTER | IS_IN_MACHINE)) { + + if (pmap[p->sCols[i]][p->sRows[j]].flags & (HAS_ITEM | HAS_MONSTER | IS_IN_MACHINE)) { // Abort if we've entered another machine or engulfed another machine's item or monster. tryAgain = true; qualifyingTileCount = totalFreq; // This is a hack to drop out of these three for-loops. @@ -1141,56 +1164,63 @@ boolean buildAMachine(enum machineTypes bp, } } } - + // Now make sure the interior map satisfies the machine's qualifications. if ((blueprintCatalog[bp].flags & BP_TREAT_AS_BLOCKING) - && levelIsDisconnectedWithBlockingMap(interior, false)) { + && levelIsDisconnectedWithBlockingMap(p->interior, false)) { tryAgain = true; } else if ((blueprintCatalog[bp].flags & BP_REQUIRE_BLOCKING) - && levelIsDisconnectedWithBlockingMap(interior, true) < 100) { + && levelIsDisconnectedWithBlockingMap(p->interior, true) < 100) { tryAgain = true; // BP_REQUIRE_BLOCKING needs some work to make sure the disconnect is interesting. } // If locationFailsafe runs out, tryAgain will still be true, and we'll try a different machine. // If we're not choosing the blueprint, then don't bother with the locationFailsafe; just use the higher-level failsafe. } while (chooseBP && tryAgain && --locationFailsafe); } - + // If something went wrong, but we haven't been charged with choosing blueprint OR location, // then there is nothing to try again, so just fail. if (tryAgain && !chooseBP && !chooseLocation) { if (distanceMap) { freeGrid(distanceMap); } + free(p); return false; } - + // Now loop if necessary. } while (tryAgain); - + // This is the point of no return. Back up the level so it can be restored if we have to abort this machine after this point. - copyMap(pmap, levelBackup); - + copyMap(pmap, p->levelBackup); + // Perform any transformations to the interior indicated by the blueprint flags, including expanding the interior if requested. - prepareInteriorWithMachineFlags(interior, originX, originY, blueprintCatalog[bp].flags, blueprintCatalog[bp].dungeonProfileType); - + prepareInteriorWithMachineFlags(p->interior, originX, originY, blueprintCatalog[bp].flags, blueprintCatalog[bp].dungeonProfileType); + // If necessary, label the interior as IS_IN_AREA_MACHINE or IS_IN_ROOM_MACHINE and mark down the number. machineNumber = ++rogue.machineNumber; // Reserve this machine number, starting with 1. for(i=0; iinterior[i][j]) { pmap[i][j].flags |= ((blueprintCatalog[bp].flags & BP_ROOM) ? IS_IN_ROOM_MACHINE : IS_IN_AREA_MACHINE); pmap[i][j].machineNumber = machineNumber; // also clear any secret doors, since they screw up distance mapping and aren't fun inside machines if (pmap[i][j].layers[DUNGEON] == SECRET_DOOR) { pmap[i][j].layers[DUNGEON] = DOOR; } + // Clear wired tiles in case we stole them from another machine. + for (layer = 0; layer < NUMBER_TERRAIN_LAYERS; layer++) { + if (tileCatalog[pmap[i][j].layers[layer]].mechFlags & (TM_IS_WIRED | TM_IS_CIRCUIT_BREAKER)) { + pmap[i][j].layers[layer] = (layer == DUNGEON ? FLOOR : NOTHING); + } + } } } } - + // DEBUG printf("\n\nWorking on blueprint %i, with origin at (%i, %i). Here's the initial interior map:", bp, originX, originY); // DEBUG logBuffer(interior); - + // Calculate the distance map (so that features that want to be close to or far from the origin can be placed accordingly) // and figure out the 33rd and 67th percentiles for features that want to be near or far from the origin. if (!distanceMap) { @@ -1200,13 +1230,13 @@ boolean buildAMachine(enum machineTypes bp, calculateDistances(distanceMap, originX, originY, T_PATHING_BLOCKER, NULL, true, true); qualifyingTileCount = 0; for (i=0; i<100; i++) { - distances[i] = 0; + p->distances[i] = 0; } for(i=0; iinterior[i][j] && distanceMap[i][j] < 100) { - distances[distanceMap[i][j]]++; // create a histogram of distances -- poor man's sort function + p->distances[distanceMap[i][j]]++; // create a histogram of distances -- poor man's sort function qualifyingTileCount++; } } @@ -1214,26 +1244,26 @@ boolean buildAMachine(enum machineTypes bp, distance25 = (int) (qualifyingTileCount / 4); distance75 = (int) (3 * qualifyingTileCount / 4); for (i=0; i<100; i++) { - if (distance25 <= distances[i]) { + if (distance25 <= p->distances[i]) { distance25 = i; break; } else { - distance25 -= distances[i]; + distance25 -= p->distances[i]; } } for (i=0; i<100; i++) { - if (distance75 <= distances[i]) { + if (distance75 <= p->distances[i]) { distance75 = i; break; } else { - distance75 -= distances[i]; + distance75 -= p->distances[i]; } } //DEBUG printf("\nDistances calculated: 33rd percentile of distance is %i, and 67th is %i.", distance25, distance75); - + // Now decide which features will be skipped -- of the features marked MF_ALTERNATIVE, skip all but one, chosen randomly. // Then repeat and do the same with respect to MF_ALTERNATIVE_2, to provide up to two independent sets of alternative features per machine. - + for (i=0; ioccupied); + // Now tick through the features and build them. for (feat = 0; feat < blueprintCatalog[bp].featureCount; feat++) { - + if (skipFeature[feat]) { continue; // Skip the alternative features that were not selected for building. } - + feature = &(blueprintCatalog[bp].feature[feat]); - + // Figure out the distance bounds. distanceBound[0] = 0; distanceBound[1] = 10000; @@ -1284,25 +1314,25 @@ boolean buildAMachine(enum machineTypes bp, if (feature->flags & MF_FAR_FROM_ORIGIN) { distanceBound[0] = distance75; } - + if (feature->flags & (MF_IN_VIEW_OF_ORIGIN | MF_IN_PASSABLE_VIEW_OF_ORIGIN)) { - zeroOutGrid(viewMap); + zeroOutGrid(p->viewMap); if (feature->flags & MF_IN_PASSABLE_VIEW_OF_ORIGIN) { - getFOVMask(viewMap, originX, originY, max(DCOLS, DROWS), T_PATHING_BLOCKER, 0, false); + getFOVMask(p->viewMap, originX, originY, max(DCOLS, DROWS) * FP_FACTOR, T_PATHING_BLOCKER, 0, false); } else { - getFOVMask(viewMap, originX, originY, max(DCOLS, DROWS), (T_OBSTRUCTS_PASSABILITY | T_OBSTRUCTS_VISION), 0, false); + getFOVMask(p->viewMap, originX, originY, max(DCOLS, DROWS) * FP_FACTOR, (T_OBSTRUCTS_PASSABILITY | T_OBSTRUCTS_VISION), 0, false); } - viewMap[originX][originY] = true; - + p->viewMap[originX][originY] = true; + if (D_INSPECT_MACHINES) { dumpLevelToScreen(); - hiliteCharGrid(viewMap, &omniscienceColor, 75); - temporaryMessage("Showing visibility.", true); + hiliteCharGrid(p->viewMap, &omniscienceColor, 75); + temporaryMessage("Showing visibility.", REQUIRE_ACKNOWLEDGMENT); } } - + do { // If the MF_REPEAT_UNTIL_NO_PROGRESS flag is set, repeat until we fail to build the required number of instances. - + // Make a master map of candidate locations for this feature. qualifyingTileCount = 0; for(i=0; iinterior, p->occupied, p->viewMap, distanceMap, machineNumber, feature->flags, blueprintCatalog[bp].flags)) { qualifyingTileCount++; - candidates[i][j] = true; + p->candidates[i][j] = true; } else { - candidates[i][j] = false; + p->candidates[i][j] = false; } } } - + if (D_INSPECT_MACHINES) { dumpLevelToScreen(); - hiliteCharGrid(occupied, &red, 75); - hiliteCharGrid(candidates, &green, 75); - hiliteCharGrid(interior, &blue, 75); - temporaryMessage("Indicating: Occupied (red); Candidates (green); Interior (blue).", true); + hiliteCharGrid(p->occupied, &red, 75); + hiliteCharGrid(p->candidates, &green, 75); + hiliteCharGrid(p->interior, &blue, 75); + temporaryMessage("Indicating: Occupied (red); Candidates (green); Interior (blue).", REQUIRE_ACKNOWLEDGMENT); } - + if (feature->flags & MF_EVERYWHERE & ~MF_BUILD_AT_ORIGIN) { // Generate everywhere that qualifies -- instead of randomly picking tiles, keep spawning until we run out of eligible tiles. generateEverywhere = true; @@ -1336,12 +1366,12 @@ boolean buildAMachine(enum machineTypes bp, generateEverywhere = false; instanceCount = rand_range(feature->instanceCountRange[0], feature->instanceCountRange[1]); } - + // Cache the personal space constant. personalSpace = feature->personalSpace; - + for (instance = 0; (generateEverywhere || instance < instanceCount) && qualifyingTileCount > 0;) { - + // Find a location for the feature. if (feature->flags & MF_BUILD_AT_ORIGIN) { // Does the feature want to be at the origin? If so, put it there. (Just an optimization.) @@ -1351,10 +1381,11 @@ boolean buildAMachine(enum machineTypes bp, // Pick our candidate location randomly, and also strike it from // the candidates map so that subsequent instances of this same feature can't choose it. featX = -1; + featY = -1; randIndex = rand_range(1, qualifyingTileCount); for(i=0; icandidates[i][j]) { if (randIndex == 1) { // This is the place! featX = i; @@ -1369,36 +1400,36 @@ boolean buildAMachine(enum machineTypes bp, } } // Don't waste time trying the same place again whether or not this attempt succeeds. - candidates[featX][featY] = false; + p->candidates[featX][featY] = false; qualifyingTileCount--; - + DFSucceeded = terrainSucceeded = true; - + // Try to build the DF first, if any, since we don't want it to be disrupted by subsequently placed terrain. if (feature->featureDF) { DFSucceeded = spawnDungeonFeature(featX, featY, &dungeonFeatureCatalog[feature->featureDF], false, !(feature->flags & MF_PERMIT_BLOCKING)); } - + // Now try to place the terrain tile, if any. if (DFSucceeded && feature->terrain) { // Must we check for blocking? if (!(feature->flags & MF_PERMIT_BLOCKING) && ((tileCatalog[feature->terrain].flags & T_PATHING_BLOCKER) || (feature->flags & MF_TREAT_AS_BLOCKING))) { // Yes, check for blocking. - - zeroOutGrid(blockingMap); - blockingMap[featX][featY] = true; - terrainSucceeded = !levelIsDisconnectedWithBlockingMap(blockingMap, false); + + zeroOutGrid(p->blockingMap); + p->blockingMap[featX][featY] = true; + terrainSucceeded = !levelIsDisconnectedWithBlockingMap(p->blockingMap, false); } if (terrainSucceeded) { pmap[featX][featY].layers[feature->layer] = feature->terrain; } } - + // OK, if placement was successful, clear some personal space around the feature so subsequent features can't be generated too close. // Personal space of 0 means nothing gets cleared, 1 means that only the tile itself gets cleared, and 2 means the 3x3 grid centered on it. - + if (DFSucceeded && terrainSucceeded) { for (i = featX - personalSpace + 1; i <= featX + personalSpace - 1; @@ -1407,34 +1438,32 @@ boolean buildAMachine(enum machineTypes bp, j <= featY + personalSpace - 1; j++) { if (coordinatesAreInMap(i, j)) { - if (candidates[i][j]) { - brogueAssert(!occupied[i][j] || (i == originX && j == originY)); // Candidates[][] should never be true where occupied[][] is true. - candidates[i][j] = false; + if (p->candidates[i][j]) { + brogueAssert(!p->occupied[i][j] || (i == originX && j == originY)); // Candidates[][] should never be true where occupied[][] is true. + p->candidates[i][j] = false; qualifyingTileCount--; } - occupied[i][j] = true; + p->occupied[i][j] = true; } } } instance++; // we've placed an instance //DEBUG printf("\nPlaced instance #%i of feature %i at (%i, %i).", instance, feat, featX, featY); } - + if (DFSucceeded && terrainSucceeded) { // Proceed only if the terrain stuff for this instance succeeded. - + theItem = NULL; - + // Mark the feature location as part of the machine, in case it is not already inside of it. - //if (!(blueprintCatalog[bp].flags & BP_NO_INTERIOR_FLAG)) { pmap[featX][featY].flags |= ((blueprintCatalog[bp].flags & BP_ROOM) ? IS_IN_ROOM_MACHINE : IS_IN_AREA_MACHINE); pmap[featX][featY].machineNumber = machineNumber; - //} - + // Mark the feature location as impregnable if requested. if (feature->flags & MF_IMPREGNABLE) { pmap[featX][featY].flags |= IMPREGNABLE; } - + // Generate an item as necessary. if ((feature->flags & MF_GENERATE_ITEM) || (adoptiveItem && (feature->flags & MF_ADOPT_ITEM) && (blueprintCatalog[bp].flags & BP_ADOPT_ITEM))) { @@ -1449,7 +1478,7 @@ boolean buildAMachine(enum machineTypes bp, while ((theItem->flags & ITEM_CURSED) || ((feature->flags & MF_REQUIRE_GOOD_RUNIC) && (!(theItem->flags & ITEM_RUNIC))) // runic if requested || ((feature->flags & MF_NO_THROWING_WEAPONS) && theItem->category == WEAPON && theItem->quantity > 1) // no throwing weapons if prohibited - || itemIsADuplicate(theItem, spawnedItems, itemCount)) { // don't want to duplicates of rings, staffs, etc. + || itemIsADuplicate(theItem, p->spawnedItems, itemCount)) { // don't want to duplicates of rings, staffs, etc. deleteItem(theItem); theItem = generateItem(feature->itemCategory, feature->itemKind); if (failsafe <= 0) { @@ -1457,14 +1486,11 @@ boolean buildAMachine(enum machineTypes bp, } failsafe--; } - spawnedItems[itemCount] = theItem; // Keep a list of generated items so that we can delete them all if construction fails. - if (parentSpawnedItems) { - parentSpawnedItems[itemCount] = theItem; - } + p->spawnedItems[itemCount] = theItem; // Keep a list of generated items so that we can delete them all if construction fails. itemCount++; } theItem->flags |= feature->itemFlags; - + addLocationToKey(theItem, featX, featY, (feature->flags & MF_KEY_DISPOSABLE) ? true : false); theItem->originDepth = rogue.depthLevel; if (feature->flags & MF_SKELETON_KEY) { @@ -1476,7 +1502,7 @@ boolean buildAMachine(enum machineTypes bp, placeItem(theItem, featX, featY); } } - + if (feature->flags & (MF_OUTSOURCE_ITEM_TO_MACHINE | MF_BUILD_VESTIBULE)) { // Put this item up for adoption, or generate a door guard machine. // Try to create a sub-machine that qualifies. @@ -1493,50 +1519,45 @@ boolean buildAMachine(enum machineTypes bp, removeItemFromChain(theItem, floorItems); removeItemFromChain(theItem, packItems); theItem->nextItem = NULL; - success = buildAMachine(-1, -1, -1, BP_ADOPT_ITEM, theItem, spawnedItemsSub, spawnedMonstersSub); + success = buildAMachine(-1, -1, -1, BP_ADOPT_ITEM, theItem, p->spawnedItemsSub, p->spawnedMonstersSub); } else if (feature->flags & MF_BUILD_VESTIBULE) { - success = buildAMachine(-1, featX, featY, BP_VESTIBULE, NULL, spawnedItemsSub, spawnedMonstersSub); + success = buildAMachine(-1, featX, featY, BP_VESTIBULE, NULL, p->spawnedItemsSub, p->spawnedMonstersSub); } - + // Now put the item up for adoption. if (success) { // Success! Now we have to add that machine's items and monsters to our own list, so they // all get deleted if this machine or its parent fails. - for (j=0; jspawnedItemsSub[j]; j++) { + p->spawnedItems[itemCount] = p->spawnedItemsSub[j]; itemCount++; - spawnedItemsSub[j] = NULL; + p->spawnedItemsSub[j] = NULL; } - for (j=0; jspawnedMonstersSub[j]; j++) { + p->spawnedMonsters[monsterCount] = p->spawnedMonstersSub[j]; monsterCount++; - spawnedMonstersSub[j] = NULL; + p->spawnedMonstersSub[j] = NULL; } break; } } - + if (!i) { if (D_MESSAGE_MACHINE_GENERATION) printf("\nDepth %i: Failed to place blueprint %i because it requires an adoptive machine and we couldn't place one.", rogue.depthLevel, bp); // failure! abort! - copyMap(levelBackup, pmap); - abortItemsAndMonsters(spawnedItems, spawnedMonsters); + copyMap(p->levelBackup, pmap); + abortItemsAndMonsters(p->spawnedItems, p->spawnedMonsters); freeGrid(distanceMap); + free(p); return false; } theItem = NULL; } - + // Generate a horde as necessary. if ((feature->flags & MF_GENERATE_HORDE) || feature->monsterID) { - + if (feature->flags & MF_GENERATE_HORDE) { monst = spawnHorde(0, featX, @@ -1547,7 +1568,7 @@ boolean buildAMachine(enum machineTypes bp, monst->bookkeepingFlags |= MB_JUST_SUMMONED; } } - + if (feature->monsterID) { monst = monsterAtLoc(featX, featY); if (monst) { @@ -1555,18 +1576,17 @@ boolean buildAMachine(enum machineTypes bp, } monst = generateMonster(feature->monsterID, true, true); if (monst) { - monst->xLoc = featX; - monst->yLoc = featY; - pmap[monst->xLoc][monst->yLoc].flags |= HAS_MONSTER; + monst->loc = (pos){ .x = featX, .y = featY }; + pmap[monst->loc.x][monst->loc.y].flags |= HAS_MONSTER; monst->bookkeepingFlags |= MB_JUST_SUMMONED; } } - + if (monst) { if (!leader) { leader = monst; } - + // Give our item to the monster leader if appropriate. // Actually just remember that we have to give it to this monster; the actual // hand-off happens after we're sure that the machine will succeed. @@ -1575,12 +1595,11 @@ boolean buildAMachine(enum machineTypes bp, torch = theItem; } } - - for (monst = monsters->nextCreature; monst; monst = nextMonst) { - // Have to cache the next monster, as the chain can get disrupted by making a monster dormant below. - nextMonst = monst->nextCreature; + + for (creatureIterator it = iterateCreatures(monsters); hasNextCreature(it);) { + creature *monst = nextCreature(&it); if (monst->bookkeepingFlags & MB_JUST_SUMMONED) { - + // All monsters spawned by a machine are tribemates. // Assign leader/follower roles if they are not yet assigned. if (!(monst->bookkeepingFlags & (MB_LEADER | MB_FOLLOWER))) { @@ -1593,12 +1612,9 @@ boolean buildAMachine(enum machineTypes bp, leader = monst; } } - + monst->bookkeepingFlags &= ~MB_JUST_SUMMONED; - spawnedMonsters[monsterCount] = monst; - if (parentSpawnedMonsters) { - parentSpawnedMonsters[monsterCount] = monst; - } + p->spawnedMonsters[monsterCount] = monst; monsterCount++; if (feature->flags & MF_MONSTER_SLEEPING) { monst->creatureState = MONSTER_SLEEPING; @@ -1619,42 +1635,43 @@ boolean buildAMachine(enum machineTypes bp, } } theItem = NULL; - + // Finished with this instance! } } while ((feature->flags & MF_REPEAT_UNTIL_NO_PROGRESS) && instance >= feature->minimumInstanceCount); - + //DEBUG printf("\nFinished feature %i. Here's the candidates map:", feat); //DEBUG logBuffer(candidates); - + if (instance < feature->minimumInstanceCount && !(feature->flags & MF_REPEAT_UNTIL_NO_PROGRESS)) { // failure! abort! - + if (D_MESSAGE_MACHINE_GENERATION) printf("\nDepth %i: Failed to place blueprint %i because of feature %i; needed %i instances but got only %i.", rogue.depthLevel, bp, feat, feature->minimumInstanceCount, instance); - + // Restore the map to how it was before we touched it. - copyMap(levelBackup, pmap); - abortItemsAndMonsters(spawnedItems, spawnedMonsters); + copyMap(p->levelBackup, pmap); + abortItemsAndMonsters(p->spawnedItems, p->spawnedMonsters); freeGrid(distanceMap); + free(p); return false; } } - + // Clear out the interior flag for all non-wired cells, if requested. if (blueprintCatalog[bp].flags & BP_NO_INTERIOR_FLAG) { for(i=0; icarriedItem) { deleteItem(torchBearer->carriedItem); @@ -1662,9 +1679,23 @@ boolean buildAMachine(enum machineTypes bp, removeItemFromChain(torch, floorItems); torchBearer->carriedItem = torch; } - + freeGrid(distanceMap); if (D_MESSAGE_MACHINE_GENERATION) printf("\nDepth %i: Built a machine from blueprint %i with an origin at (%i, %i).", rogue.depthLevel, bp, originX, originY); + + //Pass created items and monsters to parent where they will be deleted on failure to place parent machine + if (parentSpawnedItems) { + for (i=0; ispawnedItems[i]; + } + } + if (parentSpawnedMonsters) { + for (i=0; ispawnedMonsters[i]; + } + } + + free(p); return true; } @@ -1672,31 +1703,31 @@ boolean buildAMachine(enum machineTypes bp, void addMachines() { short machineCount, failsafe; short randomMachineFactor; - + analyzeMap(true); - + // Add the amulet holder if it's depth 26: if (rogue.depthLevel == AMULET_LEVEL) { for (failsafe = 50; failsafe; failsafe--) { - if (buildAMachine(MT_AMULET_AREA, -1, -1, NULL, NULL, NULL, NULL)) { + if (buildAMachine(MT_AMULET_AREA, -1, -1, 0, NULL, NULL, NULL)) { break; } } } - + // Add reward rooms, if any: machineCount = 0; while (rogue.depthLevel <= AMULET_LEVEL - && (rogue.rewardRoomsGenerated + machineCount) * 4 + 2 < rogue.depthLevel * MACHINES_FACTOR) { + && (rogue.rewardRoomsGenerated + machineCount) * 4 + 2 < rogue.depthLevel * MACHINES_FACTOR / FP_FACTOR) { // try to build at least one every four levels on average machineCount++; } randomMachineFactor = (rogue.depthLevel < 3 && (rogue.rewardRoomsGenerated + machineCount) == 0 ? 40 : 15); - while (rand_percent(max(randomMachineFactor, 15 * MACHINES_FACTOR)) && machineCount < 100) { + while (rand_percent(max(randomMachineFactor, 15 * MACHINES_FACTOR / FP_FACTOR)) && machineCount < 100) { randomMachineFactor = 15; machineCount++; } - + for (failsafe = 50; machineCount && failsafe; failsafe--) { if (buildAMachine(-1, -1, -1, BP_REWARD, NULL, NULL, NULL)) { machineCount--; @@ -1712,67 +1743,67 @@ void runAutogenerators(boolean buildAreaMachines) { short AG, count, x, y, i; const autoGenerator *gen; char grid[DCOLS][DROWS]; - + // Cycle through the autoGenerators. for (AG=1; AGmachine > 0 == buildAreaMachines) { - + // Enforce depth constraints. if (rogue.depthLevel < gen->minDepth || rogue.depthLevel > gen->maxDepth) { continue; } - + // Decide how many of this AG to build. count = min((gen->minNumberIntercept + rogue.depthLevel * gen->minNumberSlope) / 100, gen->maxNumber); while (rand_percent(gen->frequency) && count < gen->maxNumber) { count++; } - + // Build that many instances. for (i = 0; i < count; i++) { - + // Find a location for DFs and terrain generations. //if (randomMatchingLocation(&x, &y, gen->requiredDungeonFoundationType, NOTHING, -1)) { //if (randomMatchingLocation(&x, &y, -1, -1, gen->requiredDungeonFoundationType)) { if (randomMatchingLocation(&x, &y, gen->requiredDungeonFoundationType, gen->requiredLiquidFoundationType, -1)) { - + // Spawn the DF. if (gen->DFType) { spawnDungeonFeature(x, y, &(dungeonFeatureCatalog[gen->DFType]), false, true); - + if (D_INSPECT_LEVELGEN) { dumpLevelToScreen(); hiliteCell(x, y, &yellow, 50, true); - temporaryMessage("Dungeon feature added.", true); + temporaryMessage("Dungeon feature added.", REQUIRE_ACKNOWLEDGMENT); } } - + // Spawn the terrain if it's got the priority to spawn there and won't disrupt connectivity. if (gen->terrain && tileCatalog[pmap[x][y].layers[gen->layer]].drawPriority >= tileCatalog[gen->terrain].drawPriority) { - + // Check connectivity. zeroOutGrid(grid); grid[x][y] = true; if (!(tileCatalog[gen->terrain].flags & T_PATHING_BLOCKER) || !levelIsDisconnectedWithBlockingMap(grid, false)) { - + // Build! pmap[x][y].layers[gen->layer] = gen->terrain; - + if (D_INSPECT_LEVELGEN) { dumpLevelToScreen(); hiliteCell(x, y, &yellow, 50, true); - temporaryMessage("Terrain added.", true); + temporaryMessage("Terrain added.", REQUIRE_ACKNOWLEDGMENT); } } } } - + // Attempt to build the machine if requested. // Machines will find their own locations, so it will not be at the same place as terrain and DF. if (gen->machine > 0) { @@ -1788,32 +1819,32 @@ void cleanUpLakeBoundaries() { short i, j, x, y, failsafe, layer; boolean reverse, madeChange; unsigned long subjectFlags; - + reverse = true; - + failsafe = 100; do { madeChange = false; reverse = !reverse; failsafe--; - + for (i = (reverse ? DCOLS - 2 : 1); (reverse ? i > 0 : i < DCOLS - 1); (reverse ? i-- : i++)) { - + for (j = (reverse ? DROWS - 2 : 1); (reverse ? j > 0 : j < DROWS - 1); (reverse ? j-- : j++)) { - + //assert(i >= 1 && i <= DCOLS - 2 && j >= 1 && j <= DROWS - 2); - + //if (cellHasTerrainFlag(i, j, T_OBSTRUCTS_PASSABILITY) if (cellHasTerrainFlag(i, j, T_LAKE_PATHING_BLOCKER | T_OBSTRUCTS_PASSABILITY) && !cellHasTMFlag(i, j, TM_IS_SECRET) && !(pmap[i][j].flags & IMPREGNABLE)) { - + subjectFlags = terrainFlags(i, j) & (T_LAKE_PATHING_BLOCKER | T_OBSTRUCTS_PASSABILITY); - + x = y = 0; if ((terrainFlags(i - 1, j) & T_LAKE_PATHING_BLOCKER & ~subjectFlags) && !cellHasTMFlag(i - 1, j, TM_IS_SECRET) @@ -1844,7 +1875,7 @@ void cleanUpLakeBoundaries() { void removeDiagonalOpenings() { short i, j, k, x1, y1, x2, layer; boolean diagonalCornerRemoved; - + do { diagonalCornerRemoved = false; for (i=0; i 6 && rand_percent(50)) { drawCircleOnGrid(grid, DCOLS/2, DROWS/2, rand_range(3, radius - 3), 0); @@ -2023,30 +2054,30 @@ void designChunkyRoom(short **grid) { short i, x, y; short minX, maxX, minY, maxY; short chunkCount = rand_range(2, 8); - + fillGrid(grid, 0); drawCircleOnGrid(grid, DCOLS/2, DROWS/2, 2, 1); minX = DCOLS/2 - 3; maxX = DCOLS/2 + 3; minY = DROWS/2 - 3; maxY = DROWS/2 + 3; - + for (i=0; icorridorChance), doorSites, theDP->roomFrequencies); - + if (D_INSPECT_LEVELGEN) { colorOverDungeon(&darkGray); hiliteGrid(roomMap, &blue, 100); - if (doorSites[0][0] != -1) plotCharWithColor('^', mapToWindowX(doorSites[0][0]), mapToWindowY(doorSites[0][1]), &black, &green); - if (doorSites[1][0] != -1) plotCharWithColor('v', mapToWindowX(doorSites[1][0]), mapToWindowY(doorSites[1][1]), &black, &green); - if (doorSites[2][0] != -1) plotCharWithColor('<', mapToWindowX(doorSites[2][0]), mapToWindowY(doorSites[2][1]), &black, &green); - if (doorSites[3][0] != -1) plotCharWithColor('>', mapToWindowX(doorSites[3][0]), mapToWindowY(doorSites[3][1]), &black, &green); - temporaryMessage("Generating this room:", true); + if (doorSites[0].x != -1) plotCharWithColor('^', mapToWindowX(doorSites[0].x), mapToWindowY(doorSites[0].y), &black, &green); + if (doorSites[1].x != -1) plotCharWithColor('v', mapToWindowX(doorSites[1].x), mapToWindowY(doorSites[1].y), &black, &green); + if (doorSites[2].x != -1) plotCharWithColor('<', mapToWindowX(doorSites[2].x), mapToWindowY(doorSites[2].y), &black, &green); + if (doorSites[3].x != -1) plotCharWithColor('>', mapToWindowX(doorSites[3].x), mapToWindowY(doorSites[3].y), &black, &green); + temporaryMessage("Generating this room:", REQUIRE_ACKNOWLEDGMENT); } - + // Slide hyperspace across real space, in a random but predetermined order, until the room matches up with a wall. for (i = 0; i < DCOLS*DROWS; i++) { x = sCoord[i] / DROWS; y = sCoord[i] % DROWS; - + dir = directionOfDoorSite(grid, x, y); oppDir = oppositeDirection(dir); if (dir != NO_DIRECTION - && doorSites[oppDir][0] != -1 - && roomFitsAt(grid, roomMap, x - doorSites[oppDir][0], y - doorSites[oppDir][1])) { - + && doorSites[oppDir].x != -1 + && roomFitsAt(grid, roomMap, x - doorSites[oppDir].x, y - doorSites[oppDir].y)) { + // Room fits here. if (D_INSPECT_LEVELGEN) { colorOverDungeon(&darkGray); hiliteGrid(grid, &white, 100); } - insertRoomAt(grid, roomMap, x - doorSites[oppDir][0], y - doorSites[oppDir][1], doorSites[oppDir][0], doorSites[oppDir][1]); + insertRoomAt(grid, roomMap, x - doorSites[oppDir].x, y - doorSites[oppDir].y, doorSites[oppDir].x, doorSites[oppDir].y); grid[x][y] = 2; // Door site. if (D_INSPECT_LEVELGEN) { hiliteGrid(grid, &green, 50); - temporaryMessage("Added room.", true); + temporaryMessage("Added room.", REQUIRE_ACKNOWLEDGMENT); } roomsBuilt++; break; } } } - + freeGrid(roomMap); } void adjustDungeonProfileForDepth(dungeonProfile *theProfile) { const short descentPercent = clamp(100 * (rogue.depthLevel - 1) / (AMULET_LEVEL - 1), 0, 100); - + theProfile->roomFrequencies[0] += 20 * (100 - descentPercent) / 100; theProfile->roomFrequencies[1] += 10 * (100 - descentPercent) / 100; theProfile->roomFrequencies[3] += 7 * (100 - descentPercent) / 100; theProfile->roomFrequencies[5] += 10 * descentPercent / 100; - + theProfile->corridorChance += 80 * (100 - descentPercent) / 100; } void adjustDungeonFirstRoomProfileForDepth(dungeonProfile *theProfile) { short i; const short descentPercent = clamp(100 * (rogue.depthLevel - 1) / (AMULET_LEVEL - 1), 0, 100); - + if (rogue.depthLevel == 1) { // All dungeons start with the entrance room on depth 1. for (i = 0; i < ROOM_TYPE_COUNT; i++) { @@ -2388,33 +2417,33 @@ void adjustDungeonFirstRoomProfileForDepth(dungeonProfile *theProfile) { // Parent function will translate this grid into pmap[][] to make floors, walls, doors, etc. void carveDungeon(short **grid) { dungeonProfile theDP, theFirstRoomDP; - + theDP = dungeonProfileCatalog[DP_BASIC]; adjustDungeonProfileForDepth(&theDP); - + theFirstRoomDP = dungeonProfileCatalog[DP_BASIC_FIRST_ROOM]; adjustDungeonFirstRoomProfileForDepth(&theFirstRoomDP); - + designRandomRoom(grid, false, NULL, theFirstRoomDP.roomFrequencies); - + if (D_INSPECT_LEVELGEN) { colorOverDungeon(&darkGray); hiliteGrid(grid, &white, 100); - temporaryMessage("First room placed:", true); + temporaryMessage("First room placed:", REQUIRE_ACKNOWLEDGMENT); } - + attachRooms(grid, &theDP, 35, 35); - + // colorOverDungeon(&darkGray); // hiliteGrid(grid, &white, 100); -// temporaryMessage("How does this finished level look?", true); +// temporaryMessage("How does this finished level look?", REQUIRE_ACKNOWLEDGMENT); } void finishWalls(boolean includingDiagonals) { short i, j, x1, y1; boolean foundExposure; enum directions dir; - + for (i=0; i=10; lakeMaxHeight--, lakeMaxWidth -= 2) { // lake generations - + fillGrid(grid, 0); createBlobOnGrid(grid, &lakeX, &lakeY, &lakeWidth, &lakeHeight, 5, 4, 4, lakeMaxWidth, lakeMaxHeight, 55, "ffffftttt", "ffffttttt"); - + // if (D_INSPECT_LEVELGEN) { // colorOverDungeon(&darkGray); // hiliteGrid(grid, &white, 100); -// temporaryMessage("Generated a lake.", true); +// temporaryMessage("Generated a lake.", REQUIRE_ACKNOWLEDGMENT); // } - + for (k=0; k<20; k++) { // placement attempts // propose a position for the top-left of the grid in the dungeon x = rand_range(1 - lakeX, DCOLS - lakeWidth - lakeX - 2); y = rand_range(1 - lakeY, DROWS - lakeHeight - lakeY - 2); - + if (!lakeDisruptsPassability(grid, lakeMap, -x, -y)) { // level with lake is completely connected //printf("Placed a lake!"); - + // copy in lake for (i = 0; i < lakeWidth; i++) { for (j = 0; j < lakeHeight; j++) { @@ -2606,11 +2635,11 @@ void designLakes(short **lakeMap) { } } } - + if (D_INSPECT_LEVELGEN) { dumpLevelToScreen(); hiliteGrid(lakeMap, &white, 50); - temporaryMessage("Added a lake location.", true); + temporaryMessage("Added a lake location.", REQUIRE_ACKNOWLEDGMENT); } break; } @@ -2644,7 +2673,7 @@ void fillLakes(short **lakeMap) { short deepLiquid = CRYSTAL_WALL, shallowLiquid = CRYSTAL_WALL, shallowLiquidWidth = 0; char wreathMap[DCOLS][DROWS]; short i, j; - + for (i=0; i bridgeRatioX) { // Must shorten the pathing distance enough. - + for (l=i+1; l < k; l++) { pmap[l][j].layers[LIQUID] = BRIDGE; } @@ -2766,7 +2796,7 @@ boolean buildABridge() { pmap[k][j].layers[SURFACE] = BRIDGE_EDGE; return true; } - + // try a vertical bridge foundExposure = false; for (k = j + 1; @@ -2778,7 +2808,7 @@ boolean buildABridge() { && cellHasTerrainFlag(i-1, k, (T_CAN_BE_BRIDGED | T_OBSTRUCTS_PASSABILITY)) && cellHasTerrainFlag(i+1, k, (T_CAN_BE_BRIDGED | T_OBSTRUCTS_PASSABILITY)); k++) { - + if (!cellHasTerrainFlag(i-1, k, T_OBSTRUCTS_PASSABILITY) && !cellHasTerrainFlag(i+1, k, T_OBSTRUCTS_PASSABILITY)) { foundExposure = true; @@ -2790,7 +2820,7 @@ boolean buildABridge() { && !cellHasTerrainFlag(i, k, T_PATHING_BLOCKER | T_CAN_BE_BRIDGED) && !pmap[i][k].machineNumber // Cannot end in a machine. && 100 * pathingDistance(i, j, i, k, T_PATHING_BLOCKER) / (k - j) > bridgeRatioY) { - + for (l=j+1; l < k; l++) { pmap[i][l].layers[LIQUID] = BRIDGE; } @@ -2808,22 +2838,22 @@ boolean buildABridge() { // Finishing touches -- items, monsters, staircases, etc. -- are handled elsewhere. void digDungeon() { short i, j; - + short **grid; - + rogue.machineNumber = 0; - + topBlobMinX = topBlobMinY = blobWidth = blobHeight = 0; - + #ifdef AUDIT_RNG char RNGMessage[100]; sprintf(RNGMessage, "\n\n\nDigging dungeon level %i:\n", rogue.depthLevel); RNGLog(RNGMessage); #endif - + // Clear level and fill with granite clearLevel(); - + grid = allocGrid(); carveDungeon(grid); addLoops(grid, 20); @@ -2837,88 +2867,88 @@ void digDungeon() { } } freeGrid(grid); - + finishWalls(false); - + if (D_INSPECT_LEVELGEN) { dumpLevelToScreen(); - temporaryMessage("Carved into the granite:", true); + temporaryMessage("Carved into the granite:", REQUIRE_ACKNOWLEDGMENT); } //DEBUG printf("\n%i loops created.", numLoops); //DEBUG logLevel(); - + // Time to add lakes and chasms. Strategy is to generate a series of blob lakes of decreasing size. For each lake, // propose a position, and then check via a flood fill that the level would remain connected with that placement (i.e. that // each passable tile can still be reached). If not, make 9 more placement attempts before abandoning that lake // and proceeding to generate the next smaller one. // Canvas sizes start at 30x15 and decrease by 2x1 at a time down to a minimum of 20x10. Min generated size is always 4x4. - + // DEBUG logLevel(); - + // Now design the lakes and then fill them with various liquids (lava, water, chasm, brimstone). short **lakeMap = allocGrid(); designLakes(lakeMap); fillLakes(lakeMap); freeGrid(lakeMap); - + // Run the non-machine autoGenerators. runAutogenerators(false); - + // Remove diagonal openings. removeDiagonalOpenings(); - + if (D_INSPECT_LEVELGEN) { dumpLevelToScreen(); - temporaryMessage("Diagonal openings removed.", true); + temporaryMessage("Diagonal openings removed.", REQUIRE_ACKNOWLEDGMENT); } - + // Now add some treasure machines. addMachines(); - + if (D_INSPECT_LEVELGEN) { dumpLevelToScreen(); - temporaryMessage("Machines added.", true); + temporaryMessage("Machines added.", REQUIRE_ACKNOWLEDGMENT); } - + // Run the machine autoGenerators. runAutogenerators(true); - + // Now knock down the boundaries between similar lakes where possible. cleanUpLakeBoundaries(); - + if (D_INSPECT_LEVELGEN) { dumpLevelToScreen(); - temporaryMessage("Lake boundaries cleaned up.", true); + temporaryMessage("Lake boundaries cleaned up.", REQUIRE_ACKNOWLEDGMENT); } - + // Now add some bridges. while (buildABridge()); - + if (D_INSPECT_LEVELGEN) { dumpLevelToScreen(); - temporaryMessage("Bridges added.", true); + temporaryMessage("Bridges added.", REQUIRE_ACKNOWLEDGMENT); } - + // Now remove orphaned doors and upgrade some doors to secret doors finishDoors(); - + // Now finish any exposed granite with walls and revert any unexposed walls to granite finishWalls(true); - + if (D_INSPECT_LEVELGEN) { dumpLevelToScreen(); - temporaryMessage("Finishing touches added. Level has been generated.", true); + temporaryMessage("Finishing touches added. Level has been generated.", REQUIRE_ACKNOWLEDGMENT); } } void updateMapToShore() { short i, j; short **costMap; - + rogue.updatedMapToShoreThisTurn = true; - + costMap = allocGrid(); - + // Calculate the map to shore for this level if (!rogue.mapToShore) { rogue.mapToShore = allocGrid(); @@ -2945,15 +2975,15 @@ void updateMapToShore() { // and then one waypoint is recalculated per turn thereafter. void refreshWaypoint(short wpIndex) { short **costMap; - creature *monst; - + costMap = allocGrid(); populateGenericCostMap(costMap); - for (monst = monsters->nextCreature; monst != NULL; monst = monst->nextCreature) { + for (creatureIterator it = iterateCreatures(monsters); hasNextCreature(it);) { + creature* monst = nextCreature(&it); if ((monst->creatureState == MONSTER_SLEEPING || (monst->info.flags & MONST_IMMOBILE) || (monst->bookkeepingFlags & MB_CAPTIVE)) - && costMap[monst->xLoc][monst->yLoc] >= 0) { - - costMap[monst->xLoc][monst->yLoc] = PDS_FORBIDDEN; + && costMap[monst->loc.x][monst->loc.y] >= 0) { + + costMap[monst->loc.x][monst->loc.y] = PDS_FORBIDDEN; } } fillGrid(rogue.wpDistance[wpIndex], 30000); @@ -2965,7 +2995,7 @@ void refreshWaypoint(short wpIndex) { void setUpWaypoints() { short i, j, sCoord[DCOLS * DROWS], x, y; char grid[DCOLS][DROWS]; - + zeroOutGrid(grid); for (i=0; i= tileCatalog[surfaceTileType].drawPriority) ) { - + if ((tileCatalog[surfaceTileType].flags & T_IS_FIRE) && !(tileCatalog[pmap[i][j].layers[layer]].flags & T_IS_FIRE)) { pmap[i][j].flags |= CAUGHT_FIRE_THIS_TURN; } - + if ((tileCatalog[pmap[i][j].layers[layer]].flags & T_PATHING_BLOCKER) != (tileCatalog[surfaceTileType].flags & T_PATHING_BLOCKER)) { - + rogue.staleLoopMap = true; } - + pmap[i][j].layers[layer] = surfaceTileType; // Place the terrain! accomplishedSomething = true; - + if (refresh) { refreshDungeonCell(i, j); - if (player.xLoc == i && player.yLoc == j && !player.status[STATUS_LEVITATING] && refresh) { - flavorMessage(tileFlavor(player.xLoc, player.yLoc)); + if (player.loc.x == i && player.loc.y == j && !player.status[STATUS_LEVITATING] && refresh) { + flavorMessage(tileFlavor(player.loc.x, player.loc.y)); } if (pmap[i][j].flags & (HAS_MONSTER)) { monst = monsterAtLoc(i, j); @@ -3214,14 +3244,14 @@ void spawnMapDF(short x, short y, short startProb, short probDec, char spawnMap[DCOLS][DROWS]) { - + short i, j, dir, t, x2, y2; boolean madeChange; - + spawnMap[x][y] = t = 1; // incremented before anything else happens - + madeChange = true; - + while (madeChange && startProb > 0) { madeChange = false; t++; @@ -3235,7 +3265,7 @@ void spawnMapDF(short x, short y, && (!requirePropTerrain || (propagationTerrain > 0 && cellHasTerrainType(x2, y2, propagationTerrain))) && (!cellHasTerrainFlag(x2, y2, T_OBSTRUCTS_SURFACE_EFFECTS) || (propagationTerrain > 0 && cellHasTerrainType(x2, y2, propagationTerrain))) && rand_percent(startProb)) { - + spawnMap[x2][y2] = t; madeChange = true; } @@ -3257,19 +3287,22 @@ void spawnMapDF(short x, short y, t = 2; } } + if (requirePropTerrain && !cellHasTerrainType(x, y, propagationTerrain)) { + spawnMap[x][y] = 0; + } } void evacuateCreatures(char blockingMap[DCOLS][DROWS]) { - short i, j, newLoc[2]; creature *monst; - - for (i=0; ixLoc = newLoc[0]; - monst->yLoc = newLoc[1]; + monst->loc = newLoc; pmap[i][j].flags &= ~(HAS_MONSTER | HAS_PLAYER); - pmap[newLoc[0]][newLoc[1]].flags |= (monst == &player ? HAS_PLAYER : HAS_MONSTER); + pmap[newLoc.x][newLoc.y].flags |= (monst == &player ? HAS_PLAYER : HAS_MONSTER); } } } @@ -3292,26 +3324,25 @@ boolean spawnDungeonFeature(short x, short y, dungeonFeature *feat, boolean refr char blockingMap[DCOLS][DROWS]; boolean blocking; boolean succeeded; - creature *monst; - + if ((feat->flags & DFF_RESURRECT_ALLY) && !resurrectAlly(x, y)) { return false; } - + if (feat->description[0] && !feat->messageDisplayed && playerCanSee(x, y)) { feat->messageDisplayed = true; - message(feat->description, false); + message(feat->description, 0); } - + zeroOutGrid(blockingMap); - + // Blocking keeps track of whether to abort if it turns out that the DF would obstruct the level. blocking = ((abortIfBlocking && !(feat->flags & DFF_PERMIT_BLOCKING) && ((tileCatalog[feat->tile].flags & (T_PATHING_BLOCKER)) || (feat->flags & DFF_TREAT_AS_BLOCKING))) ? true : false); - + if (feat->tile) { if (feat->layer == GAS) { pmap[x][y].volume += feat->startProbability; @@ -3331,7 +3362,7 @@ boolean spawnDungeonFeature(short x, short y, dungeonFeature *feat, boolean refr if (feat->flags & DFF_EVACUATE_CREATURES_FIRST) { // first, evacuate creatures if necessary, so that they do not re-trigger the tile. evacuateCreatures(blockingMap); } - + //succeeded = fillSpawnMap(feat->layer, feat->tile, blockingMap, (feat->flags & DFF_BLOCKED_BY_OTHER_LAYERS), refreshCell, (feat->flags & DFF_SUPERPRIORITY)); fillSpawnMap(feat->layer, feat->tile, @@ -3351,7 +3382,7 @@ boolean spawnDungeonFeature(short x, short y, dungeonFeature *feat, boolean refr evacuateCreatures(blockingMap); } } - + if (succeeded && (feat->flags & DFF_CLEAR_OTHER_TERRAIN)) { for (i=0; iflags & DFF_AGGRAVATES_MONSTERS) && feat->effectRadius) { aggravateMonsters(feat->effectRadius, x, y, &gray); @@ -3377,11 +3408,11 @@ boolean spawnDungeonFeature(short x, short y, dungeonFeature *feat, boolean refr createFlare(x, y, feat->lightFlare); } } - + if (refreshCell && (tileCatalog[feat->tile].flags & (T_IS_FIRE | T_AUTO_DESCENT)) - && cellHasTerrainFlag(player.xLoc, player.yLoc, (T_IS_FIRE | T_AUTO_DESCENT))) { - + && cellHasTerrainFlag(player.loc.x, player.loc.y, (T_IS_FIRE | T_AUTO_DESCENT))) { + applyInstantTileEffectsToCreature(&player); } if (rogue.gameHasEnded) { @@ -3389,7 +3420,7 @@ boolean spawnDungeonFeature(short x, short y, dungeonFeature *feat, boolean refr } // if (succeeded && feat->description[0] && !feat->messageDisplayed && playerCanSee(x, y)) { // feat->messageDisplayed = true; - // message(feat->description, false); + // message(feat->description, 0); // } if (succeeded) { if (feat->subsequentDF) { @@ -3407,17 +3438,17 @@ boolean spawnDungeonFeature(short x, short y, dungeonFeature *feat, boolean refr } if (feat->tile && (tileCatalog[feat->tile].flags & (T_IS_DEEP_WATER | T_LAVA_INSTA_DEATH | T_AUTO_DESCENT))) { - + rogue.updatedMapToShoreThisTurn = false; } - + // awaken dormant creatures? if (feat->flags & DFF_ACTIVATE_DORMANT_MONSTER) { - for (monst = dormantMonsters->nextCreature; monst != NULL; monst = monst->nextCreature) { - if (monst->xLoc == x && monst->yLoc == y || blockingMap[monst->xLoc][monst->yLoc]) { + for (creatureIterator it = iterateCreatures(dormantMonsters); hasNextCreature(it);) { + creature *monst = nextCreature(&it); + if (monst->loc.x == x && monst->loc.y == y || blockingMap[monst->loc.x][monst->loc.y]) { // found it! toggleMonsterDormancy(monst); - monst = dormantMonsters; } } } @@ -3426,27 +3457,29 @@ boolean spawnDungeonFeature(short x, short y, dungeonFeature *feat, boolean refr } void restoreMonster(creature *monst, short **mapToStairs, short **mapToPit) { - short i, *x, *y, turnCount;//, loc[2]; - creature *leader; + short i, *x, *y, turnCount; boolean foundLeader = false; short **theMap; enum directions dir; - - x = &(monst->xLoc); - y = &(monst->yLoc); - + + x = &(monst->loc.x); + y = &(monst->loc.y); + if (monst->status[STATUS_ENTERS_LEVEL_IN] > 0) { if (monst->bookkeepingFlags & (MB_APPROACHING_PIT)) { theMap = mapToPit; } else { theMap = mapToStairs; } + + pmap[*x][*y].flags &= ~HAS_MONSTER; if (theMap) { - turnCount = ((theMap[monst->xLoc][monst->yLoc] * monst->movementSpeed / 100) - monst->status[STATUS_ENTERS_LEVEL_IN]); + // STATUS_ENTERS_LEVEL_IN accounts for monster speed; convert back to map distance and subtract from distance to stairs + turnCount = (theMap[monst->loc.x][monst->loc.y] - (monst->status[STATUS_ENTERS_LEVEL_IN] * 100 / monst->movementSpeed)); for (i=0; i < turnCount; i++) { - if ((dir = nextStep(theMap, monst->xLoc, monst->yLoc, NULL, true)) != NO_DIRECTION) { - monst->xLoc += nbDirs[dir][0]; - monst->yLoc += nbDirs[dir][1]; + if ((dir = nextStep(theMap, monst->loc.x, monst->loc.y, NULL, true)) != NO_DIRECTION) { + monst->loc.x += nbDirs[dir][0]; + monst->loc.y += nbDirs[dir][1]; } else { break; } @@ -3454,30 +3487,31 @@ void restoreMonster(creature *monst, short **mapToStairs, short **mapToPit) { } monst->bookkeepingFlags |= MB_PREPLACED; } - - if ((pmap[*x][*y].flags & (HAS_PLAYER | HAS_UP_STAIRS | HAS_DOWN_STAIRS)) + + if ((pmap[*x][*y].flags & (HAS_PLAYER | HAS_STAIRS)) || (monst->bookkeepingFlags & MB_PREPLACED)) { - + if (!(monst->bookkeepingFlags & MB_PREPLACED)) { // (If if it's preplaced, it won't have set the HAS_MONSTER flag in the first place, // so clearing it might screw up an existing monster.) pmap[*x][*y].flags &= ~HAS_MONSTER; } getQualifyingPathLocNear(x, y, *x, *y, true, T_DIVIDES_LEVEL & avoidedFlagsForMonster(&(monst->info)), 0, - avoidedFlagsForMonster(&(monst->info)), (HAS_MONSTER | HAS_PLAYER | HAS_UP_STAIRS | HAS_DOWN_STAIRS), true); + avoidedFlagsForMonster(&(monst->info)), (HAS_MONSTER | HAS_PLAYER | HAS_STAIRS), true); } pmap[*x][*y].flags |= HAS_MONSTER; monst->bookkeepingFlags &= ~(MB_PREPLACED | MB_APPROACHING_DOWNSTAIRS | MB_APPROACHING_UPSTAIRS | MB_APPROACHING_PIT | MB_ABSORBING); monst->status[STATUS_ENTERS_LEVEL_IN] = 0; monst->corpseAbsorptionCounter = 0; - + if ((monst->bookkeepingFlags & MB_SUBMERGED) && !cellHasTMFlag(*x, *y, TM_ALLOWS_SUBMERGING)) { monst->bookkeepingFlags &= ~MB_SUBMERGED; } - + if (monst->bookkeepingFlags & MB_FOLLOWER) { // is the leader on the same level? - for (leader = monsters->nextCreature; leader != NULL; leader = leader->nextCreature) { + for (creatureIterator it = iterateCreatures(monsters); hasNextCreature(it);) { + creature *leader = nextCreature(&it); if (leader == monst->leader) { foundLeader = true; break; @@ -3492,20 +3526,20 @@ void restoreMonster(creature *monst, short **mapToStairs, short **mapToPit) { } void restoreItem(item *theItem) { - short *x, *y, loc[2]; - x = &(theItem->xLoc); - y = &(theItem->yLoc); - if (theItem->flags & ITEM_PREPLACED) { theItem->flags &= ~ITEM_PREPLACED; - getQualifyingLocNear(loc, *x, *y, true, 0, (T_OBSTRUCTS_ITEMS | T_AUTO_DESCENT | T_IS_DEEP_WATER | T_LAVA_INSTA_DEATH), - (HAS_MONSTER | HAS_ITEM | HAS_UP_STAIRS | HAS_DOWN_STAIRS), true, false); - *x = loc[0]; - *y = loc[1]; + + pos loc; + // Items can fall into deep water, enclaved lakes, another chasm, even lava! + getQualifyingLocNear(&loc, theItem->loc.x, theItem->loc.y, true, 0, + (T_OBSTRUCTS_ITEMS), + (HAS_MONSTER | HAS_ITEM | HAS_STAIRS), false, false); + + theItem->loc = loc; } - pmap[*x][*y].flags |= HAS_ITEM; - if (theItem->flags & ITEM_MAGIC_DETECTED && itemMagicChar(theItem)) { - pmap[*x][*y].flags |= ITEM_DETECTED; + pmap[theItem->loc.x][theItem->loc.y].flags |= HAS_ITEM; + if (theItem->flags & ITEM_MAGIC_DETECTED && itemMagicPolarity(theItem)) { + pmap[theItem->loc.x][theItem->loc.y].flags |= ITEM_DETECTED; } } @@ -3513,11 +3547,11 @@ void restoreItem(item *theItem) { // is not a pathing blocker, the two diagonals between the three cardinal walls are also walls, and none of the eight neighbors are in machines. boolean validStairLoc(short x, short y) { short newX, newY, dir, neighborWallCount; - + if (x < 1 || x >= DCOLS - 1 || y < 1 || y >= DROWS - 1 || pmap[x][y].layers[DUNGEON] != WALL) { return false; } - + for (dir=0; dir< DIRECTION_COUNT; dir++) { newX = x + nbDirs[dir][0]; newY = y + nbDirs[dir][1]; @@ -3525,12 +3559,12 @@ boolean validStairLoc(short x, short y) { return false; } } - + neighborWallCount = 0; for (dir=0; dir<4; dir++) { newX = x + nbDirs[dir][0]; newY = y + nbDirs[dir][1]; - + if (cellHasTerrainFlag(newX, newY, T_OBSTRUCTS_PASSABILITY)) { // neighbor is a wall neighborWallCount++; @@ -3541,13 +3575,13 @@ boolean validStairLoc(short x, short y) { return false; } // now check the two diagonals between the walls - + newX = x - nbDirs[dir][0] + nbDirs[dir][1]; newY = y - nbDirs[dir][1] + nbDirs[dir][0]; if (!cellHasTerrainFlag(newX, newY, T_OBSTRUCTS_PASSABILITY)) { return false; } - + newX = x - nbDirs[dir][0] - nbDirs[dir][1]; newY = y - nbDirs[dir][1] - nbDirs[dir][0]; if (!cellHasTerrainFlag(newX, newY, T_OBSTRUCTS_PASSABILITY)) { @@ -3565,7 +3599,7 @@ boolean validStairLoc(short x, short y) { // Grid is zeroed out within 5 spaces in all directions. void prepareForStairs(short x, short y, char grid[DCOLS][DROWS]) { short newX, newY, dir; - + // Add torches to either side. for (dir=0; dir<4; dir++) { if (!cellHasTerrainFlag(x + nbDirs[dir][0], y + nbDirs[dir][1], T_OBSTRUCTS_PASSABILITY)) { @@ -3600,16 +3634,15 @@ void prepareForStairs(short x, short y, char grid[DCOLS][DROWS]) { // Places the player, monsters, items and stairs. void initializeLevel() { short i, j, dir; - short upLoc[2], downLoc[2], **mapToStairs, **mapToPit; - creature *monst; + short **mapToStairs, **mapToPit; item *theItem; char grid[DCOLS][DROWS]; short n = rogue.depthLevel - 1; - + // Place the stairs. - - for (i=0; i < DCOLS; i++) { - for (j=0; j < DROWS; j++) { + + for (int i=0; i < DCOLS; i++) { + for (int j=0; j < DROWS; j++) { grid[i][j] = validStairLoc(i, j); } } @@ -3617,72 +3650,69 @@ void initializeLevel() { if (D_INSPECT_LEVELGEN) { dumpLevelToScreen(); hiliteCharGrid(grid, &teal, 100); - temporaryMessage("Stair location candidates:", true); + temporaryMessage("Stair location candidates:", REQUIRE_ACKNOWLEDGMENT); } - - if (getQualifyingGridLocNear(downLoc, levels[n].downStairsLoc[0], levels[n].downStairsLoc[1], grid, false)) { - prepareForStairs(downLoc[0], downLoc[1], grid); + + pos downLoc; + if (getQualifyingGridLocNear(&downLoc, levels[n].downStairsLoc.x, levels[n].downStairsLoc.y, grid, false)) { + prepareForStairs(downLoc.x, downLoc.y, grid); } else { - getQualifyingLocNear(downLoc, levels[n].downStairsLoc[0], levels[n].downStairsLoc[1], false, 0, + getQualifyingLocNear(&downLoc, levels[n].downStairsLoc.x, levels[n].downStairsLoc.y, false, 0, (T_OBSTRUCTS_PASSABILITY | T_OBSTRUCTS_ITEMS | T_AUTO_DESCENT | T_IS_DEEP_WATER | T_LAVA_INSTA_DEATH | T_IS_DF_TRAP), - (HAS_MONSTER | HAS_ITEM | HAS_UP_STAIRS | HAS_DOWN_STAIRS | IS_IN_MACHINE), true, false); + (HAS_MONSTER | HAS_ITEM | HAS_STAIRS | IS_IN_MACHINE), true, false); } - + if (rogue.depthLevel == DEEPEST_LEVEL) { - pmap[downLoc[0]][downLoc[1]].layers[DUNGEON] = DUNGEON_PORTAL; + pmap[downLoc.x][downLoc.y].layers[DUNGEON] = DUNGEON_PORTAL; } else { - pmap[downLoc[0]][downLoc[1]].layers[DUNGEON] = DOWN_STAIRS; + pmap[downLoc.x][downLoc.y].layers[DUNGEON] = DOWN_STAIRS; } - pmap[downLoc[0]][downLoc[1]].layers[LIQUID] = NOTHING; - pmap[downLoc[0]][downLoc[1]].layers[SURFACE] = NOTHING; - + pmap[downLoc.x][downLoc.y].layers[LIQUID] = NOTHING; + pmap[downLoc.x][downLoc.y].layers[SURFACE] = NOTHING; + if (!levels[n+1].visited) { - levels[n+1].upStairsLoc[0] = downLoc[0]; - levels[n+1].upStairsLoc[1] = downLoc[1]; - } - levels[n].downStairsLoc[0] = downLoc[0]; - levels[n].downStairsLoc[1] = downLoc[1]; - - if (getQualifyingGridLocNear(upLoc, levels[n].upStairsLoc[0], levels[n].upStairsLoc[1], grid, false)) { - prepareForStairs(upLoc[0], upLoc[1], grid); + levels[n+1].upStairsLoc = downLoc; + } + levels[n].downStairsLoc = downLoc; + + pos upLoc; + if (getQualifyingGridLocNear(&upLoc, levels[n].upStairsLoc.x, levels[n].upStairsLoc.y, grid, false)) { + prepareForStairs(upLoc.x, upLoc.y, grid); } else { // Hopefully this never happens. - getQualifyingLocNear(upLoc, levels[n].upStairsLoc[0], levels[n].upStairsLoc[1], false, 0, + getQualifyingLocNear(&upLoc, levels[n].upStairsLoc.x, levels[n].upStairsLoc.y, false, 0, (T_OBSTRUCTS_PASSABILITY | T_OBSTRUCTS_ITEMS | T_AUTO_DESCENT | T_IS_DEEP_WATER | T_LAVA_INSTA_DEATH | T_IS_DF_TRAP), - (HAS_MONSTER | HAS_ITEM | HAS_UP_STAIRS | HAS_DOWN_STAIRS | IS_IN_MACHINE), true, false); + (HAS_MONSTER | HAS_ITEM | HAS_STAIRS | IS_IN_MACHINE), true, false); } - - levels[n].upStairsLoc[0] = upLoc[0]; - levels[n].upStairsLoc[1] = upLoc[1]; - + + levels[n].upStairsLoc = upLoc; + if (rogue.depthLevel == 1) { - pmap[upLoc[0]][upLoc[1]].layers[DUNGEON] = DUNGEON_EXIT; + pmap[upLoc.x][upLoc.y].layers[DUNGEON] = DUNGEON_EXIT; } else { - pmap[upLoc[0]][upLoc[1]].layers[DUNGEON] = UP_STAIRS; - } - pmap[upLoc[0]][upLoc[1]].layers[LIQUID] = NOTHING; - pmap[upLoc[0]][upLoc[1]].layers[SURFACE] = NOTHING; - - rogue.downLoc[0] = downLoc[0]; - rogue.downLoc[1] = downLoc[1]; - pmap[downLoc[0]][downLoc[1]].flags |= HAS_DOWN_STAIRS; - rogue.upLoc[0] = upLoc[0]; - rogue.upLoc[1] = upLoc[1]; - pmap[upLoc[0]][upLoc[1]].flags |= HAS_UP_STAIRS; - + pmap[upLoc.x][upLoc.y].layers[DUNGEON] = UP_STAIRS; + } + pmap[upLoc.x][upLoc.y].layers[LIQUID] = NOTHING; + pmap[upLoc.x][upLoc.y].layers[SURFACE] = NOTHING; + + rogue.downLoc = downLoc; + pmap[downLoc.x][downLoc.y].flags |= HAS_STAIRS; + rogue.upLoc = upLoc; + pmap[upLoc.x][upLoc.y].flags |= HAS_STAIRS; + if (!levels[rogue.depthLevel-1].visited) { - + // Run a field of view check from up stairs so that monsters do not spawn within sight of it. for (dir=0; dir<4; dir++) { - if (coordinatesAreInMap(upLoc[0] + nbDirs[dir][0], upLoc[1] + nbDirs[dir][1]) - && !cellHasTerrainFlag(upLoc[0] + nbDirs[dir][0], upLoc[1] + nbDirs[dir][1], T_OBSTRUCTS_PASSABILITY)) { - - upLoc[0] += nbDirs[dir][0]; - upLoc[1] += nbDirs[dir][1]; + if (coordinatesAreInMap(upLoc.x + nbDirs[dir][0], upLoc.y + nbDirs[dir][1]) + && !cellHasTerrainFlag(upLoc.x + nbDirs[dir][0], upLoc.y + nbDirs[dir][1], T_OBSTRUCTS_PASSABILITY)) { + + upLoc.x += nbDirs[dir][0]; + upLoc.y += nbDirs[dir][1]; break; } } zeroOutGrid(grid); - getFOVMask(grid, upLoc[0], upLoc[1], max(DCOLS, DROWS), (T_OBSTRUCTS_VISION), 0, false); + getFOVMask(grid, upLoc.x, upLoc.y, max(DCOLS, DROWS) * FP_FACTOR, (T_OBSTRUCTS_VISION), 0, false); for (i=0; inextItem; theItem != NULL; theItem = theItem->nextItem) { restoreItem(theItem); } - + // Restore creatures that fell from the previous depth or that have been pathing toward the stairs. mapToStairs = allocGrid(); fillGrid(mapToStairs, 0); mapToPit = allocGrid(); fillGrid(mapToPit, 0); - calculateDistances(mapToStairs, player.xLoc, player.yLoc, T_PATHING_BLOCKER, NULL, true, true); + calculateDistances(mapToStairs, player.loc.x, player.loc.y, T_PATHING_BLOCKER, NULL, true, true); calculateDistances(mapToPit, - levels[rogue.depthLevel - 1].playerExitedVia[0], - levels[rogue.depthLevel - 1].playerExitedVia[1], + levels[rogue.depthLevel - 1].playerExitedVia.x, + levels[rogue.depthLevel - 1].playerExitedVia.y, T_PATHING_BLOCKER, NULL, true, true); - for (monst = monsters->nextCreature; monst != NULL; monst = monst->nextCreature) { + for (creatureIterator it = iterateCreatures(monsters); hasNextCreature(it);) { + creature *monst = nextCreature(&it); restoreMonster(monst, mapToStairs, mapToPit); } freeGrid(mapToStairs); @@ -3731,7 +3762,7 @@ boolean randomMatchingLocation(short *x, short *y, short dungeonType, short liqu *y = rand_range(0, DROWS - 1); } while (failsafeCount < 500 && ((terrainType >= 0 && !cellHasTerrainType(*x, *y, terrainType)) || (((dungeonType >= 0 && pmap[*x][*y].layers[DUNGEON] != dungeonType) || (liquidType >= 0 && pmap[*x][*y].layers[LIQUID] != liquidType)) && terrainType < 0) - || (pmap[*x][*y].flags & (HAS_PLAYER | HAS_MONSTER | HAS_DOWN_STAIRS | HAS_UP_STAIRS | HAS_ITEM | IS_IN_MACHINE)) + || (pmap[*x][*y].flags & (HAS_PLAYER | HAS_MONSTER | HAS_STAIRS | HAS_ITEM | IS_IN_MACHINE)) || (terrainType < 0 && !(tileCatalog[dungeonType].flags & T_OBSTRUCTS_ITEMS) && cellHasTerrainFlag(*x, *y, T_OBSTRUCTS_ITEMS)))); if (failsafeCount >= 500) { diff --git a/src/brogue/Bot.c b/src/brogue/Bot.c index 03f47fc..0bcb3d2 100644 --- a/src/brogue/Bot.c +++ b/src/brogue/Bot.c @@ -122,13 +122,13 @@ static lua_Integer checkCell(lua_State *L, int i) { } static enum tileType hideSecrets(enum tileType tt) { - char ch = tileCatalog[tt].displayChar; + enum displayGlyph ch = tileCatalog[tt].displayChar; char *desc = tileCatalog[tt].description; // weak but general check for secret tiles - if (ch == WALL_CHAR && strcmp(desc, "a stone wall") == 0) { + if (ch == G_WALL && strcmp(desc, "a stone wall") == 0) { return WALL; - } else if (ch == FLOOR_CHAR && strcmp(desc, "the ground") == 0) { + } else if (ch == G_FLOOR && strcmp(desc, "the ground") == 0) { return FLOOR; } else if (ch == 0 && strcmp(desc, tileCatalog[SHALLOW_WATER].description) == 0) { return SHALLOW_WATER; @@ -139,14 +139,14 @@ static enum tileType hideSecrets(enum tileType tt) { // push an item table onto the Lua stack. the existence of item is assumed to be somehow known. static void pushItem(lua_State *L, item *it) { - boolean carried = itemIsCarried(it), visible = carried || playerCanSee(it->xLoc, it->yLoc); + boolean carried = itemIsCarried(it), visible = carried || playerCanSee(it->loc.x, it->loc.y); lua_newtable(L); - uchar magicChar = itemMagicChar(it); - if (magicChar != 0 && (it->flags & ITEM_MAGIC_DETECTED)) { + int magicPolarity = itemMagicPolarity(it); + if (magicPolarity != 0 && (it->flags & ITEM_MAGIC_DETECTED)) { lua_pushboolean(L, true); - lua_setfield(L, -2, (magicChar == GOOD_MAGIC_CHAR ? "blessed" : "cursed")); + lua_setfield(L, -2, (magicPolarity == 1 ? "blessed" : "cursed")); } if (carried) { @@ -154,7 +154,7 @@ static void pushItem(lua_State *L, item *it) { lua_pushstring(L, letter); lua_setfield(L, -2, "letter"); } else { - lua_pushinteger(L, DROWS * it->xLoc + it->yLoc + 1); + lua_pushinteger(L, DROWS * it->loc.x + it->loc.y + 1); lua_setfield(L, -2, "cell"); } @@ -207,8 +207,8 @@ static void pushItem(lua_State *L, item *it) { lua_setfield(L, -2, "maxbasedamage"); float power = flags & ITEM_IDENTIFIED ? netEnchant(it) : strengthModifier(it); - float dmgfactor = pow(WEAPON_ENCHANT_DAMAGE_FACTOR, power), - accfactor = pow(WEAPON_ENCHANT_ACCURACY_FACTOR, power); + float dmgfactor = damageFraction(power) / FP_FACTOR, + accfactor = accuracyFraction(power) / FP_FACTOR; lua_pushinteger(L, it->damage.lowerBound * dmgfactor); lua_setfield(L, -2, "mindamage"); @@ -271,7 +271,7 @@ static void pushItem(lua_State *L, item *it) { lua_pushinteger(L, it->quantity); lua_setfield(L, -2, "quantity"); - itemTable *table = tableForItemCategory(c, NULL); + itemTable *table = tableForItemCategory(c); if (table != NULL) { table = &table[it->kind]; @@ -290,8 +290,7 @@ static short creatureAccuracy(creature *cr) { if (cr == &player && rogue.weapon) { float ench = rogue.weapon->flags & ITEM_IDENTIFIED ? netEnchant(rogue.weapon) : strengthModifier(rogue.weapon); - return player.info.accuracy * - pow(WEAPON_ENCHANT_ACCURACY_FACTOR, ench + FLOAT_FUDGE); + return player.info.accuracy * accuracyFraction(ench) / FP_FACTOR; } else { return monsterAccuracyAdjusted(cr); } @@ -304,7 +303,7 @@ static short playerKnownDefense() { } else { short def = (armorTable[rogue.armor->kind].range.upperBound + armorTable[rogue.armor->kind].range.lowerBound) / 2 + - 10 * (strengthModifier(rogue.armor) - player.status[STATUS_DONNING] + FLOAT_FUDGE); + 10 * (strengthModifier(rogue.armor) / FP_FACTOR - player.status[STATUS_DONNING] * FP_FACTOR); if (def < 0) def = 0; return def; } @@ -317,7 +316,7 @@ static short playerKnownDefense() { static void pushCreature(lua_State *L, creature *cr) { lua_newtable(L); - lua_pushinteger(L, cr->xLoc * DROWS + cr->yLoc + 1); + lua_pushinteger(L, cr->loc.x * DROWS + cr->loc.y + 1); lua_setfield(L, -2, "cell"); // psychic emanation @@ -452,8 +451,8 @@ static int l_iskindknown(lua_State *L) { enum itemCategory cat = luaL_checkinteger(L, 1); short kind = luaL_checkinteger(L, 2); - short nkinds; - itemTable *table = tableForItemCategory(cat, &nkinds); + short nkinds = itemKindCount(cat, 0); + itemTable *table = tableForItemCategory(cat); if (!table) { // invalid category (gold, lumenstone or amulet); just return true lua_pushboolean(L, true); @@ -546,9 +545,9 @@ static int l_getitems(lua_State *L) { for (item *it = floorItems->nextItem; it != NULL; it = it->nextItem) { // only give info on items that can be seen or are magic-detected - if (!playerCanSee(it->xLoc, it->yLoc) && !(it->flags & ITEM_MAGIC_DETECTED)) continue; + if (!playerCanSee(it->loc.x, it->loc.y) && !(it->flags & ITEM_MAGIC_DETECTED)) continue; pushItem(L, it); - lua_seti(L, -2, it->xLoc * DROWS + it->yLoc + 1); + lua_seti(L, -2, it->loc.x * DROWS + it->loc.y + 1); } return 1; } @@ -556,15 +555,17 @@ static int l_getitems(lua_State *L) { static int l_getcreatures(lua_State *L) { lua_newtable(L); - for (creature *cr = monsters->nextCreature; cr != NULL; cr = cr->nextCreature) { + for (creatureIterator it = iterateCreatures(monsters); hasNextCreature(it);) { + creature *cr = nextCreature(&it); if (!(canSeeMonster(cr) || monsterRevealed(cr))) continue; pushCreature(L, cr); - lua_seti(L, -2, cr->xLoc * DROWS + cr->yLoc + 1); + lua_seti(L, -2, cr->loc.x * DROWS + cr->loc.y + 1); } - for (creature *cr = dormantMonsters->nextCreature; cr != NULL; cr = cr->nextCreature) { + for (creatureIterator it = iterateCreatures(dormantMonsters); hasNextCreature(it);) { + creature *cr = nextCreature(&it); if (!(canSeeMonster(cr) || monsterRevealed(cr))) continue; pushCreature(L, cr); - lua_seti(L, -2, cr->xLoc * DROWS + cr->yLoc + 1); + lua_seti(L, -2, cr->loc.x * DROWS + cr->loc.y + 1); } return 1; @@ -579,7 +580,7 @@ static int l_getplayer(lua_State *L) { lua_setfield(L, -2, "turn"); lua_pushinteger(L, rogue.strength); lua_setfield(L, -2, "strength"); - lua_pushinteger(L, rogue.aggroRange); + lua_pushinteger(L, rogue.stealthRange); lua_setfield(L, -2, "stealthrange"); lua_pushinteger(L, botAction); diff --git a/src/brogue/Buttons.c b/src/brogue/Buttons.c index 0fb3ac2..e1c6963 100644 --- a/src/brogue/Buttons.c +++ b/src/brogue/Buttons.c @@ -29,7 +29,7 @@ // Draws the smooth gradient that appears on a button when you hover over or depress it. // Returns the percentage by which the current tile should be averaged toward a hilite color. short smoothHiliteGradient(const short currentXValue, const short maxXValue) { - return (short) (100 * sin(PI * currentXValue / (maxXValue))); + return (short) (100 * sin(3.14159265 * currentXValue / maxXValue)); } // Draws the button to the screen, or to a display buffer if one is given. @@ -38,34 +38,28 @@ short smoothHiliteGradient(const short currentXValue, const short maxXValue) { // Hovering highlight augments fore and back colors with buttonHoverColor by 20%. // Pressed darkens the middle color (or turns it the hover color if the button is black). void drawButton(brogueButton *button, enum buttonDrawStates highlight, cellDisplayBuffer dbuf[COLS][ROWS]) { - short i, textLoc, width, midPercent, symbolNumber, opacity, oldRNG; - color fColor, bColor, fColorBase, bColorBase, bColorEdge, bColorMid; - uchar displayCharacter; - if (!(button->flags & B_DRAW)) { return; } //assureCosmeticRNG; - oldRNG = rogue.RNG; + short oldRNG = rogue.RNG; rogue.RNG = RNG_COSMETIC; - - symbolNumber = 0; - - width = strLenWithoutEscapes(button->text); - bColorBase = button->buttonColor; - fColorBase = ((button->flags & B_ENABLED) ? white : gray); - + + const int width = strLenWithoutEscapes(button->text); + color bColorBase = button->buttonColor; + color fColorBase = ((button->flags & B_ENABLED) ? white : gray); + if (highlight == BUTTON_HOVER && (button->flags & B_HOVER_ENABLED)) { //applyColorAugment(&fColorBase, &buttonHoverColor, 20); //applyColorAugment(&bColorBase, &buttonHoverColor, 20); applyColorAverage(&fColorBase, &buttonHoverColor, 25); applyColorAverage(&bColorBase, &buttonHoverColor, 25); } - - bColorEdge = bColorBase; - bColorMid = bColorBase; + + color bColorEdge = bColorBase; + color bColorMid = bColorBase; applyColorAverage(&bColorEdge, &black, 50); - + if (highlight == BUTTON_PRESSED) { applyColorAverage(&bColorMid, &black, 75); if (COLOR_DIFF(bColorMid, bColorBase) < 50) { @@ -73,46 +67,48 @@ void drawButton(brogueButton *button, enum buttonDrawStates highlight, cellDispl applyColorAverage(&bColorMid, &buttonHoverColor, 50); } } - bColor = bColorMid; - - opacity = button->opacity; + color bColor = bColorMid; + + short opacity = button->opacity; if (highlight == BUTTON_HOVER || highlight == BUTTON_PRESSED) { opacity = 100 - ((100 - opacity) * opacity / 100); // Apply the opacity twice. } - - for (i = textLoc = 0; i < width && i + button->x < COLS; i++, textLoc++) { + + short symbolNumber = 0; + + for (int i = 0, textLoc = 0; i < width && i + button->x < COLS; i++, textLoc++) { while (button->text[textLoc] == COLOR_ESCAPE) { textLoc = decodeMessageColor(button->text, textLoc, &fColorBase); } - - fColor = fColorBase; - + + color fColor = fColorBase; + if (button->flags & B_GRADIENT) { - midPercent = smoothHiliteGradient(i, width - 1); + const int midPercent = smoothHiliteGradient(i, width - 1); bColor = bColorEdge; applyColorAverage(&bColor, &bColorMid, midPercent); } - + if (highlight == BUTTON_PRESSED) { applyColorAverage(&fColor, &bColor, 30); } - + if (button->opacity < 100) { applyColorAverage(&fColor, &bColor, 100 - opacity); } - + bakeColor(&fColor); bakeColor(&bColor); separateColors(&fColor, &bColor); - - displayCharacter = button->text[textLoc]; + + enum displayGlyph displayCharacter = button->text[textLoc]; if (button->text[textLoc] == '*') { if (button->symbol[symbolNumber]) { displayCharacter = button->symbol[symbolNumber]; } symbolNumber++; } - + if (coordinatesAreInWindow(button->x + i, button->y)) { if (dbuf) { plotCharToBuffer(displayCharacter, button->x + i, button->y, &fColor, &bColor, dbuf); @@ -126,7 +122,6 @@ void drawButton(brogueButton *button, enum buttonDrawStates highlight, cellDispl } void initializeButton(brogueButton *button) { - memset((void *) button, 0, sizeof( brogueButton )); button->text[0] = '\0'; button->flags |= (B_ENABLED | B_GRADIENT | B_HOVER_ENABLED | B_DRAW | B_KEYPRESS_HIGHLIGHT); @@ -135,10 +130,8 @@ void initializeButton(brogueButton *button) { } void drawButtonsInState(buttonState *state) { - short i; - // Draw the buttons to the dbuf: - for (i=0; i < state->buttonCount; i++) { + for (int i=0; i < state->buttonCount; i++) { if (state->buttons[i].flags & B_DRAW) { drawButton(&(state->buttons[i]), BUTTON_NORMAL, state->dbuf); } @@ -152,8 +145,6 @@ void initializeButtonState(buttonState *state, short winY, short winWidth, short winHeight) { - short i, j; - // Initialize variables for the state struct: state->buttonChosen = state->buttonFocused = state->buttonDepressed = -1; state->buttonCount = buttonCount; @@ -161,17 +152,17 @@ void initializeButtonState(buttonState *state, state->winY = winY; state->winWidth = winWidth; state->winHeight = winHeight; - for (i=0; i < state->buttonCount; i++) { + for (int i=0; i < state->buttonCount; i++) { state->buttons[i] = buttons[i]; } copyDisplayBuffer(state->rbuf, displayBuffer); clearDisplayBuffer(state->dbuf); - + drawButtonsInState(state); - + // Clear the rbuf so that it resets only those parts of the screen in which buttons are drawn in the first place: - for (i=0; irbuf[i][j].opacity = (state->dbuf[i][j].opacity ? 100 : 0); } } @@ -186,42 +177,42 @@ void initializeButtonState(buttonState *state, // Otherwise, returns -1. That can be if the user canceled (in which case *canceled is true), // or, more commonly, if the user's input in this particular split-second round was not decisive. short processButtonInput(buttonState *state, boolean *canceled, rogueEvent *event) { - short i, k, x, y; boolean buttonUsed = false; - + // Mouse event: if (event->eventType == MOUSE_DOWN || event->eventType == MOUSE_UP || event->eventType == MOUSE_ENTERED_CELL) { - - x = event->param1; - y = event->param2; - + + int x = event->param1; + int y = event->param2; + // Revert the button with old focus, if any. if (state->buttonFocused >= 0) { drawButton(&(state->buttons[state->buttonFocused]), BUTTON_NORMAL, state->dbuf); state->buttonFocused = -1; } - + // Find the button with new focus, if any. - for (i=0; i < state->buttonCount; i++) { - if ((state->buttons[i].flags & B_DRAW) - && (state->buttons[i].flags & B_ENABLED) - && (state->buttons[i].y == y || ((state->buttons[i].flags & B_WIDE_CLICK_AREA) && abs(state->buttons[i].y - y) <= 1)) - && x >= state->buttons[i].x - && x < state->buttons[i].x + strLenWithoutEscapes(state->buttons[i].text)) { - - state->buttonFocused = i; + int focusIndex; + for (focusIndex=0; focusIndex < state->buttonCount; focusIndex++) { + if ((state->buttons[focusIndex].flags & B_DRAW) + && (state->buttons[focusIndex].flags & B_ENABLED) + && (state->buttons[focusIndex].y == y || ((state->buttons[focusIndex].flags & B_WIDE_CLICK_AREA) && abs(state->buttons[focusIndex].y - y) <= 1)) + && x >= state->buttons[focusIndex].x + && x < state->buttons[focusIndex].x + strLenWithoutEscapes(state->buttons[focusIndex].text)) { + + state->buttonFocused = focusIndex; if (event->eventType == MOUSE_DOWN) { - state->buttonDepressed = i; // Keeps track of which button is down at the moment. Cleared on mouseup. + state->buttonDepressed = focusIndex; // Keeps track of which button is down at the moment. Cleared on mouseup. } break; } } - if (i == state->buttonCount) { // No focus this round. + if (focusIndex == state->buttonCount) { // No focus this round. state->buttonFocused = -1; } - + if (state->buttonDepressed >= 0) { if (state->buttonDepressed == state->buttonFocused) { drawButton(&(state->buttons[state->buttonDepressed]), BUTTON_PRESSED, state->dbuf); @@ -230,7 +221,7 @@ short processButtonInput(buttonState *state, boolean *canceled, rogueEvent *even // If no button is depressed, then update the appearance of the button with the new focus, if any. drawButton(&(state->buttons[state->buttonFocused]), BUTTON_HOVER, state->dbuf); } - + // Mouseup: if (event->eventType == MOUSE_UP) { if (state->buttonDepressed == state->buttonFocused && state->buttonFocused >= 0) { @@ -247,7 +238,7 @@ short processButtonInput(buttonState *state, boolean *canceled, rogueEvent *even *canceled = true; } } - + if (state->buttonFocused >= 0) { // Buttons don't hover-highlight when one is depressed, so we have to fix that when the mouse is up. drawButton(&(state->buttons[state->buttonFocused]), BUTTON_HOVER, state->dbuf); @@ -256,16 +247,16 @@ short processButtonInput(buttonState *state, boolean *canceled, rogueEvent *even } } } - + // Keystroke: if (event->eventType == KEYSTROKE) { - + // Cycle through all of the hotkeys of all of the buttons. - for (i=0; i < state->buttonCount; i++) { - for (k = 0; k < 10 && state->buttons[i].hotkey[k]; k++) { + for (int i=0; i < state->buttonCount; i++) { + for (int k = 0; k < 10 && state->buttons[i].hotkey[k]; k++) { if (event->param1 == state->buttons[i].hotkey[k]) { // This button was chosen. - + if (state->buttons[i].flags & B_DRAW) { // Restore the depressed and focused buttons. if (state->buttonDepressed >= 0) { @@ -274,28 +265,33 @@ short processButtonInput(buttonState *state, boolean *canceled, rogueEvent *even if (state->buttonFocused >= 0) { drawButton(&(state->buttons[state->buttonFocused]), BUTTON_NORMAL, state->dbuf); } - + // If the button likes to flash when keypressed: if (state->buttons[i].flags & B_KEYPRESS_HIGHLIGHT) { // Depress the chosen button. drawButton(&(state->buttons[i]), BUTTON_PRESSED, state->dbuf); - + // Update the display. overlayDisplayBuffer(state->rbuf, NULL); overlayDisplayBuffer(state->dbuf, NULL); - - // Wait for a little; then we're done. - pauseBrogue(50); + + if (!rogue.playbackMode || rogue.playbackPaused) { + // Wait for a little; then we're done. + pauseBrogue(50); + } else { + // Wait long enough for the viewer to see what was selected. + pauseAnimation(1000); + } } } - + state->buttonDepressed = i; buttonUsed = true; break; } } } - + if (!buttonUsed && (event->param1 == ESCAPE_KEY || event->param1 == ACKNOWLEDGE_KEY)) { // If the player pressed escape, we're done. @@ -304,7 +300,7 @@ short processButtonInput(buttonState *state, boolean *canceled, rogueEvent *even } } } - + if (buttonUsed) { state->buttonChosen = state->buttonDepressed; return state->buttonChosen; @@ -324,51 +320,38 @@ short buttonInputLoop(brogueButton *buttons, short winWidth, short winHeight, rogueEvent *returnEvent) { - short x, y, button; // (x, y) keeps track of the mouse location + short button; boolean canceled; rogueEvent theEvent; buttonState state = {0}; - + assureCosmeticRNG; - + canceled = false; - - x = y = -1; - initializeButtonState(&state, buttons, buttonCount, winX, winY, winWidth, winHeight); - -// short i, j; -// for (i=0; i= winX && i < winX + winWidth -// && j >= winY && j < winY + winHeight) { -// plotCharWithColor(' ', i, j, &white, &gray); -// } -// } -// } - + do { // Update the display. overlayDisplayBuffer(state.dbuf, NULL); - + // Get input. nextBrogueEvent(&theEvent, true, false, false); - + // Process the input. button = processButtonInput(&state, &canceled, &theEvent); - + // Revert the display. overlayDisplayBuffer(state.rbuf, NULL); - + } while (button == -1 && !canceled); - + if (returnEvent) { *returnEvent = theEvent; } - + //overlayDisplayBuffer(dbuf, NULL); // hangs around - + restoreRNG; - + return button; } diff --git a/src/brogue/Combat.c b/src/brogue/Combat.c index 41f21f8..04a7cdf 100644 --- a/src/brogue/Combat.c +++ b/src/brogue/Combat.c @@ -4,7 +4,7 @@ * * Created by Brian Walker on 6/11/09. * Copyright 2012. All rights reserved. - * + * * This file is part of Brogue. * * This program is free software: you can redistribute it and/or modify @@ -21,7 +21,6 @@ * along with this program. If not, see . */ -#include #include "Rogue.h" #include "IncludeGlobals.h" @@ -31,9 +30,9 @@ * higher numbers are better for them. Numbers over 100 are permitted. * * Each combatant also has a defense rating. The "hit probability" is calculated as given by this formula: - * + * * hit probability = (accuracy) * 0.987 ^ (defense) - * + * * when hit determinations are made. Negative numbers and numbers over 100 are permitted. * The hit is then randomly determined according to this final percentage. * @@ -52,76 +51,96 @@ * * Player combatants take their base defense value of their actual armor. Their accuracy is a combination of weapon, armor * and strength. - * + * * Players have a base accuracy value of 100 throughout the game. Each point of weapon enchantment (net of - * strength penalty/benefit) increases + * strength penalty/benefit) increases */ -float strengthModifier(item *theItem) { - short difference = (rogue.strength - player.weaknessAmount) - theItem->strengthRequired; +fixpt strengthModifier(item *theItem) { + int difference = (rogue.strength - player.weaknessAmount) - theItem->strengthRequired; if (difference > 0) { - return (float) 0.25 * difference; + return difference * FP_FACTOR / 4; // 0.25x } else { - return (float) 2.5 * difference; + return difference * FP_FACTOR * 5/2; // 2.5x } } -float netEnchant(item *theItem) { +fixpt netEnchant(item *theItem) { + fixpt retval = theItem->enchant1 * FP_FACTOR; if (theItem->category & (WEAPON | ARMOR)) { - return ((float) theItem->enchant1) + strengthModifier(theItem); + retval += strengthModifier(theItem); + } + // Clamp all net enchantment values to [-20, 50]. + return clamp(retval, -20 * FP_FACTOR, 50 * FP_FACTOR); +} + +fixpt monsterDamageAdjustmentAmount(const creature *monst) { + if (monst == &player) { + // Handled through player strength routines elsewhere. + return FP_FACTOR; } else { - return ((float) theItem->enchant1); + return damageFraction(monst->weaknessAmount * FP_FACTOR * -3/2); } } +short monsterDefenseAdjusted(const creature *monst) { + short retval; + if (monst == &player) { + // Weakness is already taken into account in recalculateEquipmentBonuses() for the player. + retval = monst->info.defense; + } else { + retval = monst->info.defense - 25 * monst->weaknessAmount; + } + return max(retval, 0); +} + +short monsterAccuracyAdjusted(const creature *monst) { + short retval = monst->info.accuracy * accuracyFraction(monst->weaknessAmount * FP_FACTOR * -3/2) / FP_FACTOR; + return max(retval, 0); +} + // does NOT account for auto-hit from sleeping or unaware defenders; does account for auto-hit from // stuck or captive defenders and from weapons of slaying. short hitProbability(creature *attacker, creature *defender) { short accuracy = monsterAccuracyAdjusted(attacker); short defense = monsterDefenseAdjusted(defender); short hitProbability; - + if (defender->status[STATUS_STUCK] || (defender->bookkeepingFlags & MB_CAPTIVE)) { return 100; } - if ((defender->bookkeepingFlags & MB_SEIZED) && (attacker->bookkeepingFlags & MB_SEIZING)) { - + return 100; } - if (attacker == &player && rogue.weapon) { if ((rogue.weapon->flags & ITEM_RUNIC) && rogue.weapon->enchant2 == W_SLAYING && monsterIsInClass(defender, rogue.weapon->vorpalEnemy)) { - + return 100; } - accuracy = (double) player.info.accuracy * pow(WEAPON_ENCHANT_ACCURACY_FACTOR, netEnchant(rogue.weapon) + FLOAT_FUDGE); + accuracy = player.info.accuracy * accuracyFraction(netEnchant(rogue.weapon)) / FP_FACTOR; } - - hitProbability = accuracy * pow(DEFENSE_FACTOR, defense); - + hitProbability = accuracy * defenseFraction(defense * FP_FACTOR) / FP_FACTOR; if (hitProbability > 100) { hitProbability = 100; } else if (hitProbability < 0) { hitProbability = 0; } - return hitProbability; } boolean attackHit(creature *attacker, creature *defender) { - // automatically hit if the monster is sleeping or captive or stuck in a web if (defender->status[STATUS_STUCK] || defender->status[STATUS_PARALYZED] || (defender->bookkeepingFlags & MB_CAPTIVE)) { - + return true; } - + return rand_percent(hitProbability(attacker, defender)); } @@ -129,12 +148,12 @@ void addMonsterToContiguousMonsterGrid(short x, short y, creature *monst, char g short newX, newY; enum directions dir; creature *tempMonst; - + grid[x][y] = true; for (dir=0; dir<4; dir++) { newX = x + nbDirs[dir][0]; newY = y + nbDirs[dir][1]; - + if (coordinatesAreInMap(newX, newY) && !grid[newX][newY]) { tempMonst = monsterAtLoc(newX, newY); if (tempMonst && monstersAreTeammates(monst, tempMonst)) { @@ -155,19 +174,19 @@ void splitMonster(creature *monst, short x, short y) { char monstName[DCOLS]; char monsterGrid[DCOLS][DROWS], eligibleGrid[DCOLS][DROWS]; creature *clone; - + zeroOutGrid(monsterGrid); zeroOutGrid(eligibleGrid); eligibleLocationCount = 0; - + // Add the (x, y) location to the contiguous group, if any. if (x > 0 && y > 0) { monsterGrid[x][y] = true; } - + // Find the contiguous group of monsters. - addMonsterToContiguousMonsterGrid(monst->xLoc, monst->yLoc, monst, monsterGrid); - + addMonsterToContiguousMonsterGrid(monst->loc.x, monst->loc.y, monst, monsterGrid); + // Find the eligible edges around the group of monsters. for (i=0; icurrentHP = (monst->currentHP + 1) / 2; clone = cloneMonster(monst, false, false); - + // Split monsters don't inherit the learnings of their parents. // Sorry, but self-healing jelly armies are too much. // Mutation effects can be inherited, however; they're not learned abilities. - if (monst->mutationIndex) { + if (monst->mutationIndex >= 0) { clone->info.flags &= (monsterCatalog[clone->info.monsterID].flags | mutationCatalog[monst->mutationIndex].monsterFlags); clone->info.abilityFlags &= (monsterCatalog[clone->info.monsterID].abilityFlags | mutationCatalog[monst->mutationIndex].monsterAbilityFlags); } else { @@ -220,25 +239,25 @@ void splitMonster(creature *monst, short x, short y) { for (b = 0; b < 20; b++) { clone->info.bolts[b] = monsterCatalog[clone->info.monsterID].bolts[b]; } - + if (!(clone->info.flags & MONST_FLIES) && clone->status[STATUS_LEVITATING] == 1000) { - + clone->status[STATUS_LEVITATING] = 0; } - - clone->xLoc = i; - clone->yLoc = j; + + clone->loc.x = i; + clone->loc.y = j; pmap[i][j].flags |= HAS_MONSTER; clone->ticksUntilTurn = max(clone->ticksUntilTurn, 101); fadeInMonster(clone); refreshSideBar(-1, -1, false); - + if (canDirectlySeeMonster(monst)) { sprintf(buf, "%s splits in two!", monstName); - message(buf, false); + message(buf, 0); } - + return; } } @@ -247,34 +266,34 @@ void splitMonster(creature *monst, short x, short y) { } short alliedCloneCount(creature *monst) { - short count; - creature *temp; - - count = 0; - for (temp = monsters->nextCreature; temp != NULL; temp = temp->nextCreature) { + short count = 0; + for (creatureIterator it = iterateCreatures(monsters); hasNextCreature(it);) { + creature *temp = nextCreature(&it); if (temp != monst && temp->info.monsterID == monst->info.monsterID && monstersAreTeammates(temp, monst)) { - + count++; } } - if (rogue.depthLevel > 0) { - for (temp = levels[rogue.depthLevel - 2].monsters; temp != NULL; temp = temp->nextCreature) { + if (rogue.depthLevel > 1) { + for (creatureIterator it = iterateCreatures(&levels[rogue.depthLevel - 2].monsters); hasNextCreature(it);) { + creature *temp = nextCreature(&it); if (temp != monst && temp->info.monsterID == monst->info.monsterID && monstersAreTeammates(temp, monst)) { - + count++; } } } if (rogue.depthLevel < DEEPEST_LEVEL) { - for (temp = levels[rogue.depthLevel].monsters; temp != NULL; temp = temp->nextCreature) { + for (creatureIterator it = iterateCreatures(&levels[rogue.depthLevel].monsters); hasNextCreature(it);) { + creature *temp = nextCreature(&it); if (temp != monst && temp->info.monsterID == monst->info.monsterID && monstersAreTeammates(temp, monst)) { - + count++; } } @@ -285,17 +304,17 @@ short alliedCloneCount(creature *monst) { // This function is called whenever one creature acts aggressively against another in a way that directly causes damage. // This can be things like melee attacks, fire/lightning attacks or throwing a weapon. void moralAttack(creature *attacker, creature *defender) { - - if (attacker == &player) { + + if (attacker == &player && canSeeMonster(defender)) { rogue.featRecord[FEAT_PACIFIST] = false; if (defender->creatureState != MONSTER_TRACKING_SCENT) { rogue.featRecord[FEAT_PALADIN] = false; } } - + if (defender->currentHP > 0 && !(defender->bookkeepingFlags & MB_IS_DYING)) { - + if (defender->status[STATUS_PARALYZED]) { defender->status[STATUS_PARALYZED] = 0; // Paralyzed creature gets a turn to react before the attacker moves again. @@ -305,26 +324,30 @@ void moralAttack(creature *attacker, creature *defender) { defender->status[STATUS_MAGICAL_FEAR] = 1; } defender->status[STATUS_ENTRANCED] = 0; - + + if ((defender->info.abilityFlags & MA_AVOID_CORRIDORS)) { + defender->status[STATUS_ENRAGED] = defender->maxStatus[STATUS_ENRAGED] = 4; + } + if (attacker == &player && defender->creatureState == MONSTER_ALLY && !defender->status[STATUS_DISCORDANT] && !attacker->status[STATUS_CONFUSED] && !(attacker->bookkeepingFlags & MB_IS_DYING)) { - + unAlly(defender); } - + if ((attacker == &player || attacker->creatureState == MONSTER_ALLY) && defender != &player && defender->creatureState != MONSTER_ALLY) { - + alertMonster(defender); // this alerts the monster that you're nearby } - + if ((defender->info.abilityFlags & MA_CLONE_SELF_ON_DEFEND) && alliedCloneCount(defender) < 100) { - if (distanceBetween(defender->xLoc, defender->yLoc, attacker->xLoc, attacker->yLoc) <= 1) { - splitMonster(defender, attacker->xLoc, attacker->yLoc); + if (distanceBetween(defender->loc.x, defender->loc.y, attacker->loc.x, attacker->loc.y) <= 1) { + splitMonster(defender, attacker->loc.x, attacker->loc.y); } else { splitMonster(defender, 0, 0); } @@ -338,7 +361,7 @@ boolean playerImmuneToMonster(creature *monst) { && (rogue.armor->flags & ITEM_RUNIC) && (rogue.armor->enchant2 == A_IMMUNITY) && monsterIsInClass(monst, rogue.armor->vorpalEnemy)) { - + return true; } else { return false; @@ -349,27 +372,28 @@ void specialHit(creature *attacker, creature *defender, short damage) { short itemCandidates, randItemIndex, stolenQuantity; item *theItem = NULL, *itemFromTopOfStack; char buf[COLS], buf2[COLS], buf3[COLS]; - + if (!(attacker->info.abilityFlags & SPECIAL_HIT)) { return; } - + // Special hits that can affect only the player: if (defender == &player) { if (playerImmuneToMonster(attacker)) { return; } - + if (attacker->info.abilityFlags & MA_HIT_DEGRADE_ARMOR && defender == &player && rogue.armor - && !(rogue.armor->flags & ITEM_PROTECTED)) { - + && !(rogue.armor->flags & ITEM_PROTECTED) + && (rogue.armor->enchant1 + rogue.armor->armor/10 > -10)) { + rogue.armor->enchant1--; - equipItem(rogue.armor, true); + equipItem(rogue.armor, true, NULL); itemName(rogue.armor, buf2, false, false, NULL); sprintf(buf, "your %s weakens!", buf2); - messageWithColor(buf, &itemMessageColor, false); + messageWithColor(buf, &itemMessageColor, 0); checkForDisenchantment(rogue.armor); } if (attacker->info.abilityFlags & MA_HIT_HALLUCINATE) { @@ -382,14 +406,19 @@ void specialHit(creature *attacker, creature *defender, short damage) { player.status[STATUS_HALLUCINATING] += 20; player.maxStatus[STATUS_HALLUCINATING] = max(player.maxStatus[STATUS_HALLUCINATING], player.status[STATUS_HALLUCINATING]); } - + if (attacker->info.abilityFlags & MA_HIT_BURN + && !defender->status[STATUS_IMMUNE_TO_FIRE]) { + + exposeCreatureToFire(defender); + } + if (attacker->info.abilityFlags & MA_HIT_STEAL_FLEE && !(attacker->carriedItem) && (packItems->nextItem) && attacker->currentHP > 0 && !attacker->status[STATUS_CONFUSED] // No stealing from the player if you bump him while confused. && attackHit(attacker, defender)) { - + itemCandidates = numberOfMatchingPackItems(ALL_ITEMS, 0, (ITEM_EQUIPPED), false); if (itemCandidates) { randItemIndex = rand_range(1, itemCandidates); @@ -419,6 +448,10 @@ void specialHit(creature *attacker, creature *defender, short damage) { itemFromTopOfStack->quantity = stolenQuantity; theItem = itemFromTopOfStack; // Redirect pointer. } else { + if (rogue.swappedIn == theItem || rogue.swappedOut == theItem) { + rogue.swappedIn = NULL; + rogue.swappedOut = NULL; + } removeItemFromChain(theItem, packItems); } theItem->flags &= ~ITEM_PLAYER_AVOIDS; // Explore will seek the item out if it ends up on the floor again. @@ -428,7 +461,8 @@ void specialHit(creature *attacker, creature *defender, short damage) { monsterName(buf2, attacker, true); itemName(theItem, buf3, false, true, NULL); sprintf(buf, "%s stole %s!", buf2, buf3); - messageWithColor(buf, &badMessageColor, false); + messageWithColor(buf, &badMessageColor, 0); + rogue.autoPlayingLevel = false; } } } @@ -436,117 +470,62 @@ void specialHit(creature *attacker, creature *defender, short damage) { if ((attacker->info.abilityFlags & MA_POISONS) && damage > 0 && !(defender->info.flags & (MONST_INANIMATE | MONST_INVULNERABLE))) { - + addPoison(defender, damage, 1); } if ((attacker->info.abilityFlags & MA_CAUSES_WEAKNESS) && damage > 0 && !(defender->info.flags & (MONST_INANIMATE | MONST_INVULNERABLE))) { - + weaken(defender, 300); } -} - -short runicWeaponChance(item *theItem, boolean customEnchantLevel, float enchantLevel) { - const float effectChances[NUMBER_WEAPON_RUNIC_KINDS] = { - 0.16, // W_SPEED - 0.06, // W_QUIETUS - 0.07, // W_PARALYSIS - 0.15, // W_MULTIPLICITY - 0.14, // W_SLOWING - 0.11, // W_CONFUSION - 0.15, // W_FORCE - 0, // W_SLAYING - 0, // W_MERCY - 0}; // W_PLENTY - float rootChance, modifier; - short runicType = theItem->enchant2; - short chance, adjustedBaseDamage; - - if (runicType == W_SLAYING) { - return 0; - } - if (runicType >= NUMBER_GOOD_WEAPON_ENCHANT_KINDS) { // bad runic - return 15; - } - if (!customEnchantLevel) { - enchantLevel = (float) netEnchant(theItem); - } - - rootChance = effectChances[runicType]; - - // Innately high-damage weapon types are less likely to trigger runic effects. - adjustedBaseDamage = (theItem->damage.lowerBound + theItem->damage.upperBound) / 2; - - if (theItem->flags & ITEM_ATTACKS_HIT_SLOWLY) { - adjustedBaseDamage /= 2; // Normalize as though they attacked once per turn instead of every other turn. - } -// if (theItem->flags & ITEM_ATTACKS_QUICKLY) { -// adjustedBaseDamage *= 2; // Normalize as though they attacked once per turn instead of twice per turn. -// } // Testing disabling this for balance reasons... - - modifier = 1.0 - min(0.99, ((float) adjustedBaseDamage) / 18.0); - rootChance *= modifier; - - chance = 100 - (short) (100 * pow(1.0 - rootChance, enchantLevel) + FLOAT_FUDGE); // good runic - - // Slow weapons get an adjusted chance of 1 - (1-p)^2 to reflect two bites at the apple instead of one. - if (theItem->flags & ITEM_ATTACKS_HIT_SLOWLY) { - chance = 100 - (100 - chance) * (100 - chance) / 100; - } - // Fast weapons get an adjusted chance of 1 - sqrt(1-p) to reflect one bite at the apple instead of two. - if (theItem->flags & ITEM_ATTACKS_QUICKLY) { - chance = 100 * (1.0 - sqrt(1 - ((double)(chance)/100.0))); - } - - // The lowest percent change that a weapon will ever have is its enchantment level (if greater than 0). - // That is so that even really heavy weapons will improve at least 1% per enchantment. - chance = clamp(chance, max(1, (short) enchantLevel), 100); - - return chance; + if (attacker->info.abilityFlags & MA_ATTACKS_STAGGER) { + processStaggerHit(attacker, defender); + } } boolean forceWeaponHit(creature *defender, item *theItem) { - short oldLoc[2], newLoc[2], forceDamage; + short forceDamage; char buf[DCOLS*3], buf2[COLS], monstName[DCOLS]; creature *otherMonster = NULL; boolean knowFirstMonsterDied = false, autoID = false; bolt theBolt; - + monsterName(monstName, defender, true); - - oldLoc[0] = defender->xLoc; - oldLoc[1] = defender->yLoc; - newLoc[0] = defender->xLoc + clamp(defender->xLoc - player.xLoc, -1, 1); - newLoc[1] = defender->yLoc + clamp(defender->yLoc - player.yLoc, -1, 1); + + pos oldLoc = defender->loc; + pos newLoc = (pos){ + .x = defender->loc.x + clamp(defender->loc.x - player.loc.x, -1, 1), + .y = defender->loc.y + clamp(defender->loc.y - player.loc.y, -1, 1) + }; if (canDirectlySeeMonster(defender) - && !cellHasTerrainFlag(newLoc[0], newLoc[1], T_OBSTRUCTS_PASSABILITY | T_OBSTRUCTS_VISION) - && !(pmap[newLoc[0]][newLoc[1]].flags & (HAS_MONSTER | HAS_PLAYER))) { + && !cellHasTerrainFlag(newLoc.x, newLoc.y, T_OBSTRUCTS_PASSABILITY | T_OBSTRUCTS_VISION) + && !(pmap[newLoc.x][newLoc.y].flags & (HAS_MONSTER | HAS_PLAYER))) { sprintf(buf, "you launch %s backward with the force of your blow", monstName); buf[DCOLS] = '\0'; combatMessage(buf, messageColorFromVictim(defender)); autoID = true; } theBolt = boltCatalog[BOLT_BLINKING]; - theBolt.magnitude = max(1, netEnchant(theItem) + FLOAT_FUDGE); + theBolt.magnitude = max(1, netEnchant(theItem) / FP_FACTOR); zap(oldLoc, newLoc, &theBolt, false); if (!(defender->bookkeepingFlags & MB_IS_DYING) - && distanceBetween(oldLoc[0], oldLoc[1], defender->xLoc, defender->yLoc) > 0 - && distanceBetween(oldLoc[0], oldLoc[1], defender->xLoc, defender->yLoc) < weaponForceDistance(netEnchant(theItem))) { - - if (pmap[defender->xLoc + newLoc[0] - oldLoc[0]][defender->yLoc + newLoc[1] - oldLoc[1]].flags & (HAS_MONSTER | HAS_PLAYER)) { - otherMonster = monsterAtLoc(defender->xLoc + newLoc[0] - oldLoc[0], defender->yLoc + newLoc[1] - oldLoc[1]); + && distanceBetween(oldLoc.x, oldLoc.y, defender->loc.x, defender->loc.y) > 0 + && distanceBetween(oldLoc.x, oldLoc.y, defender->loc.x, defender->loc.y) < weaponForceDistance(netEnchant(theItem))) { + + if (pmap[defender->loc.x + newLoc.x - oldLoc.x][defender->loc.y + newLoc.y - oldLoc.y].flags & (HAS_MONSTER | HAS_PLAYER)) { + otherMonster = monsterAtLoc(defender->loc.x + newLoc.x - oldLoc.x, defender->loc.y + newLoc.y - oldLoc.y); monsterName(buf2, otherMonster, true); } else { otherMonster = NULL; - strcpy(buf2, tileCatalog[pmap[defender->xLoc + newLoc[0] - oldLoc[0]][defender->yLoc + newLoc[1] - oldLoc[1]].layers[highestPriorityLayer(defender->xLoc + newLoc[0] - oldLoc[0], defender->yLoc + newLoc[1] - oldLoc[1], true)]].description); + strcpy(buf2, tileCatalog[pmap[defender->loc.x + newLoc.x - oldLoc.x][defender->loc.y + newLoc.y - oldLoc.y].layers[highestPriorityLayer(defender->loc.x + newLoc.x - oldLoc.x, defender->loc.y + newLoc.y - oldLoc.y, true)]].description); } - - forceDamage = distanceBetween(oldLoc[0], oldLoc[1], defender->xLoc, defender->yLoc); - + + forceDamage = distanceBetween(oldLoc.x, oldLoc.y, defender->loc.x, defender->loc.y); + if (!(defender->info.flags & (MONST_IMMUNE_TO_WEAPONS | MONST_INVULNERABLE)) && inflictDamage(NULL, defender, forceDamage, &white, false)) { - + if (canDirectlySeeMonster(defender)) { knowFirstMonsterDied = true; sprintf(buf, "%s %s on impact with %s", @@ -568,10 +547,10 @@ boolean forceWeaponHit(creature *defender, item *theItem) { } } moralAttack(&player, defender); - + if (otherMonster && !(defender->info.flags & (MONST_IMMUNE_TO_WEAPONS | MONST_INVULNERABLE))) { - + if (inflictDamage(NULL, otherMonster, forceDamage, &white, false)) { if (canDirectlySeeMonster(otherMonster)) { sprintf(buf, "%s %s%s when %s slams into $HIMHER", @@ -596,16 +575,16 @@ boolean forceWeaponHit(creature *defender, item *theItem) { void magicWeaponHit(creature *defender, item *theItem, boolean backstabbed) { char buf[DCOLS*3], monstName[DCOLS], theItemName[DCOLS]; - + color *effectColors[NUMBER_WEAPON_RUNIC_KINDS] = {&white, &black, &yellow, &pink, &green, &confusionGasColor, NULL, NULL, &darkRed, &rainbow}; // W_SPEED, W_QUIETUS, W_PARALYSIS, W_MULTIPLICITY, W_SLOWING, W_CONFUSION, W_FORCE, W_SLAYING, W_MERCY, W_PLENTY short chance, i; - float enchant; + fixpt enchant; enum weaponEnchants enchantType = theItem->enchant2; creature *newMonst; boolean autoID = false; - + // If the defender is already dead, proceed only if the runic is speed or multiplicity. // (Everything else acts on the victim, which would literally be overkill.) if ((defender->bookkeepingFlags & MB_IS_DYING) @@ -613,9 +592,9 @@ void magicWeaponHit(creature *defender, item *theItem, boolean backstabbed) { && theItem->enchant2 != W_MULTIPLICITY) { return; } - + enchant = netEnchant(theItem); - + if (theItem->enchant2 == W_SLAYING) { chance = (monsterIsInClass(defender, theItem->vorpalEnemy) ? 100 : 0); } else if (defender->info.flags & (MONST_INANIMATE | MONST_INVULNERABLE)) { @@ -630,13 +609,13 @@ void magicWeaponHit(creature *defender, item *theItem, boolean backstabbed) { if (!(defender->bookkeepingFlags & MB_SUBMERGED)) { switch (enchantType) { case W_SPEED: - createFlare(player.xLoc, player.yLoc, SCROLL_ENCHANTMENT_LIGHT); + createFlare(player.loc.x, player.loc.y, SCROLL_ENCHANTMENT_LIGHT); break; case W_QUIETUS: - createFlare(defender->xLoc, defender->yLoc, QUIETUS_FLARE_LIGHT); + createFlare(defender->loc.x, defender->loc.y, QUIETUS_FLARE_LIGHT); break; case W_SLAYING: - createFlare(defender->xLoc, defender->yLoc, SLAYING_FLARE_LIGHT); + createFlare(defender->loc.x, defender->loc.y, SLAYING_FLARE_LIGHT); break; default: flashMonster(defender, effectColors[enchantType], 100); @@ -647,7 +626,7 @@ void magicWeaponHit(creature *defender, item *theItem, boolean backstabbed) { rogue.disturbed = true; monsterName(monstName, defender, true); itemName(theItem, theItemName, false, false, NULL); - + switch (enchantType) { case W_SPEED: if (player.ticksUntilTurn != -1) { @@ -685,18 +664,19 @@ void magicWeaponHit(creature *defender, item *theItem, boolean backstabbed) { (weaponImageCount(enchant) == 1 ? "" : "s"), (weaponImageCount(enchant) == 1 ? "s" : "")); buf[DCOLS] = '\0'; - + for (i = 0; i < (weaponImageCount(enchant)); i++) { newMonst = generateMonster(MK_SPECTRAL_IMAGE, true, false); - getQualifyingPathLocNear(&(newMonst->xLoc), &(newMonst->yLoc), defender->xLoc, defender->yLoc, true, + getQualifyingPathLocNear(&(newMonst->loc.x), &(newMonst->loc.y), defender->loc.x, defender->loc.y, true, T_DIVIDES_LEVEL & avoidedFlagsForMonster(&(newMonst->info)), HAS_PLAYER, - avoidedFlagsForMonster(&(newMonst->info)), (HAS_PLAYER | HAS_MONSTER | HAS_UP_STAIRS | HAS_DOWN_STAIRS), false); + avoidedFlagsForMonster(&(newMonst->info)), (HAS_PLAYER | HAS_MONSTER | HAS_STAIRS), false); newMonst->bookkeepingFlags |= (MB_FOLLOWER | MB_BOUND_TO_LEADER | MB_DOES_NOT_TRACK_LEADER | MB_TELEPATHICALLY_REVEALED); newMonst->bookkeepingFlags &= ~MB_JUST_SUMMONED; newMonst->leader = &player; newMonst->creatureState = MONSTER_ALLY; - if (theItem->flags & ITEM_ATTACKS_HIT_SLOWLY) { + if (theItem->flags & ITEM_ATTACKS_STAGGER) { newMonst->info.attackSpeed *= 2; + newMonst->info.abilityFlags |= MA_ATTACKS_STAGGER; } if (theItem->flags & ITEM_ATTACKS_QUICKLY) { newMonst->info.attackSpeed /= 2; @@ -711,7 +691,7 @@ void magicWeaponHit(creature *defender, item *theItem, boolean backstabbed) { newMonst->info.abilityFlags |= MA_ATTACKS_EXTEND; } newMonst->ticksUntilTurn = 100; - newMonst->info.accuracy = player.info.accuracy + 5 * netEnchant(theItem); + newMonst->info.accuracy = player.info.accuracy + (5 * netEnchant(theItem) / FP_FACTOR); newMonst->info.damage = player.info.damage; newMonst->status[STATUS_LIFESPAN_REMAINING] = newMonst->maxStatus[STATUS_LIFESPAN_REMAINING] = weaponImageDuration(enchant); if (strLenWithoutEscapes(theItemName) <= 8) { @@ -735,12 +715,12 @@ void magicWeaponHit(creature *defender, item *theItem, boolean backstabbed) { break; } } - pmap[newMonst->xLoc][newMonst->yLoc].flags |= HAS_MONSTER; + pmap[newMonst->loc.x][newMonst->loc.y].flags |= HAS_MONSTER; fadeInMonster(newMonst); } updateVision(true); - - message(buf, false); + + message(buf, 0); autoID = true; break; case W_SLOWING: @@ -791,17 +771,17 @@ void magicWeaponHit(creature *defender, item *theItem, boolean backstabbed) { void attackVerb(char returnString[DCOLS], creature *attacker, short hitPercentile) { short verbCount, increment; - + if (attacker != &player && (player.status[STATUS_HALLUCINATING] || !canSeeMonster(attacker))) { strcpy(returnString, "hits"); return; } - + if (attacker == &player && !rogue.weapon) { strcpy(returnString, "punch"); return; } - + for (verbCount = 0; verbCount < 4 && monsterText[attacker->info.monsterID].attack[verbCount + 1][0] != '\0'; verbCount++); increment = (100 / (verbCount + 1)); hitPercentile = max(0, min(hitPercentile, increment * (verbCount + 1) - 1)); @@ -814,24 +794,24 @@ void applyArmorRunicEffect(char returnString[DCOLS], creature *attacker, short * boolean runicKnown; boolean runicDiscovered; short newDamage, dir, newX, newY, count, i; - float enchant; + fixpt enchant; creature *monst, *hitList[8]; - + returnString[0] = '\0'; - + if (!(rogue.armor && rogue.armor->flags & ITEM_RUNIC)) { return; // just in case } - + enchant = netEnchant(rogue.armor); - + runicKnown = rogue.armor->flags & ITEM_RUNIC_IDENTIFIED; runicDiscovered = false; - + itemName(rogue.armor, armorName, false, false, NULL); - + monsterName(attackerName, attacker, true); - + switch (rogue.armor->enchant2) { case A_MULTIPLICITY: if (melee && !(attacker->info.flags & (MONST_INANIMATE | MONST_INVULNERABLE)) && rand_percent(33)) { @@ -848,19 +828,20 @@ void applyArmorRunicEffect(char returnString[DCOLS], creature *attacker, short * monst->ticksUntilTurn = 100; monst->info.monsterID = MK_SPECTRAL_IMAGE; if (monst->carriedMonster) { - killCreature(monst->carriedMonster, true); // Otherwise you can get infinite phoenices from a discordant phoenix. + creature *carried = monst->carriedMonster; monst->carriedMonster = NULL; + killCreature(carried, true); // Otherwise you can get infinite phoenices from a discordant phoenix. } - + // Give it the glowy red light and color. monst->info.intrinsicLightType = SPECTRAL_IMAGE_LIGHT; monst->info.foreColor = &spectralImageColor; - + // Temporary guest! monst->status[STATUS_LIFESPAN_REMAINING] = monst->maxStatus[STATUS_LIFESPAN_REMAINING] = 3; monst->currentHP = monst->info.maxHP = 1; monst->info.defense = 0; - + if (strLenWithoutEscapes(attacker->info.monsterName) <= 6) { sprintf(monst->info.monsterName, "spectral %s", attacker->info.monsterName); } else { @@ -869,7 +850,7 @@ void applyArmorRunicEffect(char returnString[DCOLS], creature *attacker, short * fadeInMonster(monst); } updateVision(true); - + runicDiscovered = true; sprintf(returnString, "Your %s flashes, and spectral images of %s appear!", armorName, attackerName); } @@ -880,8 +861,8 @@ void applyArmorRunicEffect(char returnString[DCOLS], creature *attacker, short * for (i=0; i<8; i++) { hitList[i] = NULL; dir = i % 8; - newX = player.xLoc + nbDirs[dir][0]; - newY = player.yLoc + nbDirs[dir][1]; + newX = player.loc.x + nbDirs[dir][0]; + newY = player.loc.y + nbDirs[dir][1]; if (coordinatesAreInMap(newX, newY) && (pmap[newX][newY].flags & HAS_MONSTER)) { monst = monsterAtLoc(newX, newY); if (monst @@ -889,7 +870,7 @@ void applyArmorRunicEffect(char returnString[DCOLS], creature *attacker, short * && monstersAreEnemies(&player, monst) && !(monst->info.flags & (MONST_IMMUNE_TO_WEAPONS | MONST_INVULNERABLE)) && !(monst->bookkeepingFlags & MB_IS_DYING)) { - + hitList[i] = monst; count++; } @@ -901,7 +882,7 @@ void applyArmorRunicEffect(char returnString[DCOLS], creature *attacker, short * monsterName(monstName, hitList[i], true); if (inflictDamage(&player, hitList[i], (*damage + count) / (count + 1), &blue, true) && canSeeMonster(hitList[i])) { - + sprintf(buf, "%s %s", monstName, ((hitList[i]->info.flags & MONST_INANIMATE) ? "is destroyed" : "dies")); combatMessage(buf, messageColorFromVictim(hitList[i])); } @@ -918,7 +899,7 @@ void applyArmorRunicEffect(char returnString[DCOLS], creature *attacker, short * } break; case A_ABSORPTION: - *damage -= rand_range(0, armorAbsorptionMax(enchant)); + *damage -= rand_range(1, armorAbsorptionMax(enchant)); if (*damage <= 0) { *damage = 0; runicDiscovered = true; @@ -953,7 +934,7 @@ void applyArmorRunicEffect(char returnString[DCOLS], creature *attacker, short * if (rand_percent(10)) { rogue.armor->strengthRequired++; sprintf(returnString, "your %s suddenly feels heavier!", armorName); - equipItem(rogue.armor, true); + equipItem(rogue.armor, true, NULL); runicDiscovered = true; } break; @@ -967,15 +948,15 @@ void applyArmorRunicEffect(char returnString[DCOLS], creature *attacker, short * case A_IMMOLATION: if (rand_percent(10)) { sprintf(returnString, "flames suddenly explode out of your %s!", armorName); - message(returnString, !runicKnown); + message(returnString, runicKnown ? 0 : REQUIRE_ACKNOWLEDGMENT); returnString[0] = '\0'; - spawnDungeonFeature(player.xLoc, player.yLoc, &(dungeonFeatureCatalog[DF_ARMOR_IMMOLATION]), true, false); + spawnDungeonFeature(player.loc.x, player.loc.y, &(dungeonFeatureCatalog[DF_ARMOR_IMMOLATION]), true, false); runicDiscovered = true; } default: break; } - + if (runicDiscovered && !runicKnown) { autoIdentify(rogue.armor); } @@ -983,17 +964,34 @@ void applyArmorRunicEffect(char returnString[DCOLS], creature *attacker, short * void decrementWeaponAutoIDTimer() { char buf[COLS*3], buf2[COLS*3]; - + if (rogue.weapon && !(rogue.weapon->flags & ITEM_IDENTIFIED) && !--rogue.weapon->charges) { - + rogue.weapon->flags |= ITEM_IDENTIFIED; updateIdentifiableItems(); - messageWithColor("you are now familiar enough with your weapon to identify it.", &itemMessageColor, false); + messageWithColor("you are now familiar enough with your weapon to identify it.", &itemMessageColor, 0); itemName(rogue.weapon, buf2, true, true, NULL); sprintf(buf, "%s %s.", (rogue.weapon->quantity > 1 ? "they are" : "it is"), buf2); - messageWithColor(buf, &itemMessageColor, false); + messageWithColor(buf, &itemMessageColor, 0); + } +} + +void processStaggerHit(creature *attacker, creature *defender) { + if ((defender->info.flags & (MONST_INVULNERABLE | MONST_IMMOBILE | MONST_INANIMATE)) + || (defender->bookkeepingFlags & MB_CAPTIVE) + || cellHasTerrainFlag(defender->loc.x, defender->loc.y, T_OBSTRUCTS_PASSABILITY)) { + + return; + } + short newX = clamp(defender->loc.x - attacker->loc.x, -1, 1) + defender->loc.x; + short newY = clamp(defender->loc.y - attacker->loc.y, -1, 1) + defender->loc.y; + if (coordinatesAreInMap(newX, newY) + && !cellHasTerrainFlag(newX, newY, T_OBSTRUCTS_PASSABILITY) + && !(pmap[newX][newY].flags & (HAS_MONSTER | HAS_PLAYER))) { + + setMonsterLocation(defender, newX, newY); } } @@ -1002,47 +1000,47 @@ boolean attack(creature *attacker, creature *defender, boolean lungeAttack) { short damage, specialDamage, poisonDamage; char buf[COLS*2], buf2[COLS*2], attackerName[COLS], defenderName[COLS], verb[DCOLS], explicationClause[DCOLS] = "", armorRunicString[DCOLS*3]; boolean sneakAttack, defenderWasAsleep, defenderWasParalyzed, degradesAttackerWeapon, sightUnseen; - - if (attacker == &player) { + + if (attacker == &player && canSeeMonster(defender)) { rogue.featRecord[FEAT_PURE_MAGE] = false; } - + if (attacker->info.abilityFlags & MA_KAMIKAZE) { killCreature(attacker, false); return true; } - + armorRunicString[0] = '\0'; - + poisonDamage = 0; - + degradesAttackerWeapon = (defender->info.flags & MONST_DEFEND_DEGRADE_WEAPON ? true : false); - + sightUnseen = !canSeeMonster(attacker) && !canSeeMonster(defender); - + if (defender->status[STATUS_LEVITATING] && (attacker->info.flags & MONST_RESTRICTED_TO_LIQUID)) { return false; // aquatic or other liquid-bound monsters cannot attack flying opponents } - + if ((attacker == &player || defender == &player) && !rogue.blockCombatText) { rogue.disturbed = true; } - + defender->status[STATUS_ENTRANCED] = 0; if (defender->status[STATUS_MAGICAL_FEAR]) { defender->status[STATUS_MAGICAL_FEAR] = 1; } - + if (attacker == &player && defender->creatureState != MONSTER_TRACKING_SCENT) { - + rogue.featRecord[FEAT_PALADIN] = false; } - + if (attacker != &player && defender == &player && attacker->creatureState == MONSTER_WANDERING) { attacker->creatureState = MONSTER_TRACKING_SCENT; } - + if (defender->info.flags & MONST_INANIMATE) { sneakAttack = false; defenderWasAsleep = false; @@ -1052,27 +1050,29 @@ boolean attack(creature *attacker, creature *defender, boolean lungeAttack) { defenderWasAsleep = (defender != &player && (defender->creatureState == MONSTER_SLEEPING) ? true : false); defenderWasParalyzed = defender->status[STATUS_PARALYZED] > 0; } - + monsterName(attackerName, attacker, true); monsterName(defenderName, defender, true); - + if ((attacker->info.abilityFlags & MA_SEIZES) - && (!(attacker->bookkeepingFlags & MB_SEIZING) || !(defender->bookkeepingFlags & MB_SEIZED))) { - + && (!(attacker->bookkeepingFlags & MB_SEIZING) || !(defender->bookkeepingFlags & MB_SEIZED)) + && (distanceBetween(attacker->loc.x, attacker->loc.y, defender->loc.x, defender->loc.y) == 1 + && !diagonalBlocked(attacker->loc.x, attacker->loc.y, defender->loc.x, defender->loc.y, false))) { + attacker->bookkeepingFlags |= MB_SEIZING; defender->bookkeepingFlags |= MB_SEIZED; if (canSeeMonster(attacker) || canSeeMonster(defender)) { sprintf(buf, "%s seizes %s!", attackerName, (defender == &player ? "your legs" : defenderName)); - messageWithColor(buf, &white, false); + messageWithColor(buf, &white, 0); } return false; } - + if (sneakAttack || defenderWasAsleep || defenderWasParalyzed || lungeAttack || attackHit(attacker, defender)) { // If the attack hit: damage = (defender->info.flags & (MONST_IMMUNE_TO_WEAPONS | MONST_INVULNERABLE) - ? 0 : randClump(attacker->info.damage) * monsterDamageAdjustmentAmount(attacker)); - + ? 0 : randClump(attacker->info.damage) * monsterDamageAdjustmentAmount(attacker) / FP_FACTOR); + if (sneakAttack || defenderWasAsleep || defenderWasParalyzed) { if (defender != &player) { // The non-player defender doesn't hit back this turn because it's still flat-footed. @@ -1086,21 +1086,21 @@ boolean attack(creature *attacker, creature *defender, boolean lungeAttack) { if (attacker == &player && rogue.weapon && (rogue.weapon->flags & ITEM_SNEAK_ATTACK_BONUS)) { - - damage *= 5; // Treble damage for general sneak attacks. + + damage *= 5; // 5x damage for dagger sneak attacks. } else { damage *= 3; // Treble damage for general sneak attacks. } } - + if (defender == &player && rogue.armor && (rogue.armor->flags & ITEM_RUNIC)) { applyArmorRunicEffect(armorRunicString, attacker, &damage, true); } - + if (attacker == &player && rogue.reaping && !(defender->info.flags & (MONST_INANIMATE | MONST_INVULNERABLE))) { - + specialDamage = min(damage, defender->currentHP) * rogue.reaping; // Maximum reaped damage can't exceed the victim's remaining health. if (rogue.reaping > 0) { specialDamage = rand_range(0, specialDamage); @@ -1111,7 +1111,7 @@ boolean attack(creature *attacker, creature *defender, boolean lungeAttack) { rechargeItemsIncrementally(specialDamage); } } - + if (damage == 0) { sprintf(explicationClause, " but %s no damage", (attacker == &player ? "do" : "does")); if (attacker == &player) { @@ -1131,12 +1131,12 @@ boolean attack(creature *attacker, creature *defender, boolean lungeAttack) { (defender == &player ? "" : "s")); } resolvePronounEscapes(explicationClause, defender); - + if ((attacker->info.abilityFlags & MA_POISONS) && damage > 0) { poisonDamage = damage; damage = 1; } - + if (inflictDamage(attacker, defender, damage, &red, false)) { // if the attack killed the defender if (defenderWasAsleep || sneakAttack || defenderWasParalyzed || lungeAttack) { sprintf(buf, "%s %s %s%s", attackerName, @@ -1164,13 +1164,13 @@ boolean attack(creature *attacker, creature *defender, boolean lungeAttack) { return true; } else if (&player == attacker && defender->info.monsterID == MK_DRAGON) { - + rogue.featRecord[FEAT_DRAGONSLAYER] = true; } } else { // if the defender survived if (!rogue.blockCombatText && (canSeeMonster(attacker) || canSeeMonster(defender))) { - attackVerb(verb, attacker, max(damage - attacker->info.damage.lowerBound * monsterDamageAdjustmentAmount(attacker), 0) * 100 - / max(1, (attacker->info.damage.upperBound - attacker->info.damage.lowerBound) * monsterDamageAdjustmentAmount(attacker))); + attackVerb(verb, attacker, max(damage - (attacker->info.damage.lowerBound * monsterDamageAdjustmentAmount(attacker) / FP_FACTOR), 0) * 100 + / max(1, (attacker->info.damage.upperBound - attacker->info.damage.lowerBound) * monsterDamageAdjustmentAmount(attacker) / FP_FACTOR)); sprintf(buf, "%s %s %s%s", attackerName, verb, defenderName, explicationClause); if (sightUnseen) { if (!rogue.heardCombatThisTurn) { @@ -1181,48 +1181,52 @@ boolean attack(creature *attacker, creature *defender, boolean lungeAttack) { combatMessage(buf, messageColorFromVictim(defender)); } } + if (attacker == &player && rogue.weapon && (rogue.weapon->flags & ITEM_ATTACKS_STAGGER)) { + processStaggerHit(attacker, defender); + } if (attacker->info.abilityFlags & SPECIAL_HIT) { specialHit(attacker, defender, (attacker->info.abilityFlags & MA_POISONS) ? poisonDamage : damage); } if (armorRunicString[0]) { - message(armorRunicString, false); + message(armorRunicString, 0); if (rogue.armor && (rogue.armor->flags & ITEM_RUNIC) && rogue.armor->enchant2 == A_BURDEN) { - strengthCheck(rogue.armor); + strengthCheck(rogue.armor, true); } } } - + moralAttack(attacker, defender); - + if (attacker == &player && rogue.weapon && (rogue.weapon->flags & ITEM_RUNIC)) { magicWeaponHit(defender, rogue.weapon, sneakAttack || defenderWasAsleep || defenderWasParalyzed); } - + if (attacker == &player && (defender->bookkeepingFlags & MB_IS_DYING) && (defender->bookkeepingFlags & MB_HAS_SOUL)) { - + decrementWeaponAutoIDTimer(); } - + if (degradesAttackerWeapon && attacker == &player && rogue.weapon && !(rogue.weapon->flags & ITEM_PROTECTED) // Can't damage a Weapon of Acid Mound Slaying by attacking an acid mound... just ain't right! - && !((rogue.weapon->flags & ITEM_RUNIC) && rogue.weapon->enchant2 == W_SLAYING && monsterIsInClass(defender, rogue.weapon->vorpalEnemy))) { - + && !((rogue.weapon->flags & ITEM_RUNIC) && rogue.weapon->enchant2 == W_SLAYING && monsterIsInClass(defender, rogue.weapon->vorpalEnemy)) + && rogue.weapon->enchant1 >= -10) { + rogue.weapon->enchant1--; if (rogue.weapon->quiverNumber) { rogue.weapon->quiverNumber = rand_range(1, 60000); } - equipItem(rogue.weapon, true); + equipItem(rogue.weapon, true, NULL); itemName(rogue.weapon, buf2, false, false, NULL); sprintf(buf, "your %s weakens!", buf2); - messageWithColor(buf, &itemMessageColor, false); + messageWithColor(buf, &itemMessageColor, 0); checkForDisenchantment(rogue.weapon); } - + return true; } else { // if the attack missed if (!rogue.blockCombatText) { @@ -1243,7 +1247,7 @@ boolean attack(creature *attacker, creature *defender, boolean lungeAttack) { // Gets the length of a string without the four-character color escape sequences, since those aren't displayed. short strLenWithoutEscapes(const char *str) { short i, count; - + count = 0; for (i=0; str[i];) { if (str[i] == COLOR_ESCAPE) { @@ -1256,39 +1260,68 @@ short strLenWithoutEscapes(const char *str) { return count; } +// Buffer messages generated by combat until flushed by displayCombatText(). +// Messages in the buffer are delimited by newlines. void combatMessage(char *theMsg, color *theColor) { - char newMsg[COLS * 2]; - + short length; + char newMsg[COLS * 2 - 1]; // -1 for the newline when appending later + if (theColor == 0) { theColor = &white; } - + newMsg[0] = '\0'; encodeMessageColor(newMsg, 0, theColor); - strcat(newMsg, theMsg); - - if (strLenWithoutEscapes(combatText) + strLenWithoutEscapes(newMsg) + 3 > DCOLS) { - // the "3" is for the semicolon, space and period that get added to conjoined combat texts. + length = strlen(newMsg); + strncat(&newMsg[length], theMsg, (COLS * 2 - 1) - length - 1); + + length = strlen(combatText); + + // Buffer combat messages here just for timing; otherwise player combat + // messages appear after monsters, rather than before. The -2 is for the + // newline and terminator. + if (length + strlen(newMsg) > COLS * 2 - 2) { displayCombatText(); } - + if (combatText[0]) { - strcat(combatText, "; "); - strcat(combatText, newMsg); + snprintf(&combatText[length], COLS * 2 - length, "\n%s", newMsg); } else { strcpy(combatText, newMsg); } } +// Flush any buffered, newline-delimited combat messages, passing each to +// message(). These messages are "foldable", meaning that if space permits +// they may be joined together by semi-colons. Notice that combat messages may +// be flushed by a number of different callers. One is message() itself +// creating a recursion, which this function is responsible for terminating. void displayCombatText() { - char buf[COLS]; - - if (combatText[0]) { - sprintf(buf, "%s.", combatText); - combatText[0] = '\0'; - message(buf, rogue.cautiousMode); - rogue.cautiousMode = false; + char buf[COLS * 2]; + char *start, *end; + + // message itself will call displayCombatText. For this guard to terminate + // the recursion, we need to copy combatText out and empty it before + // calling message. + if (combatText[0] == '\0') { + return; + } + + strcpy(buf, combatText); + combatText[0] = '\0'; + + start = buf; + for (end = start; *end != '\0'; end++) { + if (*end == '\n') { + *end = '\0'; + message(start, FOLDABLE | (rogue.cautiousMode ? REQUIRE_ACKNOWLEDGMENT : 0)); + start = end + 1; + } } + + message(start, FOLDABLE | (rogue.cautiousMode ? REQUIRE_ACKNOWLEDGMENT : 0)); + + rogue.cautiousMode = false; } void flashMonster(creature *monst, const color *theColor, short strength) { @@ -1305,14 +1338,14 @@ void flashMonster(creature *monst, const color *theColor, short strength) { boolean canAbsorb(creature *ally, boolean ourBolts[NUMBER_BOLT_KINDS], creature *prey, short **grid) { short i; - + if (ally->creatureState == MONSTER_ALLY && ally->newPowerCount > 0 && (ally->targetCorpseLoc[0] <= 0) && !((ally->info.flags | prey->info.flags) & (MONST_INANIMATE | MONST_IMMOBILE)) - && !monsterAvoids(ally, prey->xLoc, prey->yLoc) - && grid[ally->xLoc][ally->yLoc] <= 10) { - + && !monsterAvoids(ally, prey->loc.x, prey->loc.y) + && grid[ally->loc.x][ally->loc.y] <= 10) { + if (~(ally->info.abilityFlags) & prey->info.abilityFlags & LEARNABLE_ABILITIES) { return true; } else if (~(ally->info.flags) & prey->info.flags & LEARNABLE_BEHAVIORS) { @@ -1324,11 +1357,11 @@ boolean canAbsorb(creature *ally, boolean ourBolts[NUMBER_BOLT_KINDS], creature for (i = 0; ally->info.bolts[i] != BOLT_NONE; i++) { ourBolts[ally->info.bolts[i]] = true; } - + for (i=0; prey->info.bolts[i] != BOLT_NONE; i++) { if (!(boltCatalog[prey->info.bolts[i]].flags & BF_NOT_LEARNABLE) && !ourBolts[prey->info.bolts[i]]) { - + return true; } } @@ -1340,83 +1373,88 @@ boolean canAbsorb(creature *ally, boolean ourBolts[NUMBER_BOLT_KINDS], creature boolean anyoneWantABite(creature *decedent) { short candidates, randIndex, i; short **grid; - creature *ally; boolean success = false; - boolean ourBolts[NUMBER_BOLT_KINDS]; - + boolean ourBolts[NUMBER_BOLT_KINDS] = {false}; + candidates = 0; if ((!(decedent->info.abilityFlags & LEARNABLE_ABILITIES) && !(decedent->info.flags & LEARNABLE_BEHAVIORS) && decedent->info.bolts[0] == BOLT_NONE) - || (cellHasTerrainFlag(decedent->xLoc, decedent->yLoc, T_PATHING_BLOCKER)) + || (cellHasTerrainFlag(decedent->loc.x, decedent->loc.y, T_PATHING_BLOCKER)) + || decedent->info.monsterID == MK_SPECTRAL_IMAGE || (decedent->info.flags & (MONST_INANIMATE | MONST_IMMOBILE))) { - + return false; } - + grid = allocGrid(); fillGrid(grid, 0); - calculateDistances(grid, decedent->xLoc, decedent->yLoc, T_PATHING_BLOCKER, NULL, true, true); - for (ally = monsters->nextCreature; ally != NULL; ally = ally->nextCreature) { - if (canAbsorb(ally, ourBolts, decedent, grid)) { // This populates ourBolts if it returns true. + calculateDistances(grid, decedent->loc.x, decedent->loc.y, T_PATHING_BLOCKER, NULL, true, true); + for (creatureIterator it = iterateCreatures(monsters); hasNextCreature(it);) { + creature *ally = nextCreature(&it); + if (canAbsorb(ally, ourBolts, decedent, grid)) { candidates++; } } if (candidates > 0) { randIndex = rand_range(1, candidates); - for (ally = monsters->nextCreature; ally != NULL; ally = ally->nextCreature) { + creature *firstAlly = NULL; + for (creatureIterator it = iterateCreatures(monsters); hasNextCreature(it);) { + creature *ally = nextCreature(&it); + // CanAbsorb() populates ourBolts if it returns true and there are no learnable behaviors or flags: if (canAbsorb(ally, ourBolts, decedent, grid) && !--randIndex) { + firstAlly = ally; break; } } - if (ally) { - ally->targetCorpseLoc[0] = decedent->xLoc; - ally->targetCorpseLoc[1] = decedent->yLoc; - strcpy(ally->targetCorpseName, decedent->info.monsterName); - ally->corpseAbsorptionCounter = 20; // 20 turns to get there and start eating before he loses interest - + if (firstAlly) { + firstAlly->targetCorpseLoc[0] = decedent->loc.x; + firstAlly->targetCorpseLoc[1] = decedent->loc.y; + strcpy(firstAlly->targetCorpseName, decedent->info.monsterName); + firstAlly->corpseAbsorptionCounter = 20; // 20 turns to get there and start eating before he loses interest + // Choose a superpower. // First, select from among learnable ability or behavior flags, if one is available. candidates = 0; for (i=0; i<32; i++) { - if (Fl(i) & ~(ally->info.abilityFlags) & decedent->info.abilityFlags & LEARNABLE_ABILITIES) { + if (Fl(i) & ~(firstAlly->info.abilityFlags) & decedent->info.abilityFlags & LEARNABLE_ABILITIES) { candidates++; } } for (i=0; i<32; i++) { - if (Fl(i) & ~(ally->info.flags) & decedent->info.flags & LEARNABLE_BEHAVIORS) { + if (Fl(i) & ~(firstAlly->info.flags) & decedent->info.flags & LEARNABLE_BEHAVIORS) { candidates++; } } if (candidates > 0) { randIndex = rand_range(1, candidates); for (i=0; i<32; i++) { - if ((Fl(i) & ~(ally->info.abilityFlags) & decedent->info.abilityFlags & LEARNABLE_ABILITIES) + if ((Fl(i) & ~(firstAlly->info.abilityFlags) & decedent->info.abilityFlags & LEARNABLE_ABILITIES) && !--randIndex) { - - ally->absorptionFlags = Fl(i); - ally->absorbBehavior = false; + + firstAlly->absorptionFlags = Fl(i); + firstAlly->absorbBehavior = false; success = true; break; } } for (i=0; i<32 && !success; i++) { - if ((Fl(i) & ~(ally->info.flags) & decedent->info.flags & LEARNABLE_BEHAVIORS) + if ((Fl(i) & ~(firstAlly->info.flags) & decedent->info.flags & LEARNABLE_BEHAVIORS) && !--randIndex) { - - ally->absorptionFlags = Fl(i); - ally->absorbBehavior = true; + + firstAlly->absorptionFlags = Fl(i); + firstAlly->absorbBehavior = true; success = true; break; } } } else if (decedent->info.bolts[0] != BOLT_NONE) { - // If there are no learnable ability or behavior flags, try to find a learnable bolt. + // If there are no learnable ability or behavior flags, pick a learnable bolt. candidates = 0; for (i=0; decedent->info.bolts[i] != BOLT_NONE; i++) { if (!(boltCatalog[decedent->info.bolts[i]].flags & BF_NOT_LEARNABLE) && !ourBolts[decedent->info.bolts[i]]) { - + candidates++; } } @@ -1426,8 +1464,8 @@ boolean anyoneWantABite(creature *decedent) { if (!(boltCatalog[decedent->info.bolts[i]].flags & BF_NOT_LEARNABLE) && !ourBolts[decedent->info.bolts[i]] && !--randIndex) { - - ally->absorptionBolt = decedent->info.bolts[i]; + + firstAlly->absorptionBolt = decedent->info.bolts[i]; success = true; break; } @@ -1450,20 +1488,20 @@ void inflictLethalDamage(creature *attacker, creature *defender) { // flashColor indicates the color that the damage will cause the creature to flash boolean inflictDamage(creature *attacker, creature *defender, short damage, const color *flashColor, boolean ignoresProtectionShield) { - + boolean killed = false; dungeonFeature theBlood; short transferenceAmount; - + if (damage == 0 || (defender->info.flags & MONST_INVULNERABLE)) { - + return false; } - + if (!ignoresProtectionShield && defender->status[STATUS_SHIELDED]) { - + if (defender->status[STATUS_SHIELDED] > damage * 10) { defender->status[STATUS_SHIELDED] -= damage * 10; damage = 0; @@ -1472,9 +1510,9 @@ boolean inflictDamage(creature *attacker, creature *defender, defender->status[STATUS_SHIELDED] = defender->maxStatus[STATUS_SHIELDED] = 0; } } - + defender->bookkeepingFlags &= ~MB_ABSORBING; // Stop eating a corpse if you are getting hurt. - + // bleed all over the place, proportionately to damage inflicted: if (damage > 0 && defender->info.bloodType) { theBlood = dungeonFeatureCatalog[defender->info.bloodType]; @@ -1482,24 +1520,24 @@ boolean inflictDamage(creature *attacker, creature *defender, if (theBlood.layer == GAS) { theBlood.startProbability *= 100; } - spawnDungeonFeature(defender->xLoc, defender->yLoc, &theBlood, true, false); + spawnDungeonFeature(defender->loc.x, defender->loc.y, &theBlood, true, false); } - + if (defender != &player && defender->creatureState == MONSTER_SLEEPING) { wakeUp(defender); } - + if (defender == &player && rogue.easyMode && damage > 0) { damage = max(1, damage/5); } - + if (((attacker == &player && rogue.transference) || (attacker && attacker != &player && (attacker->info.abilityFlags & MA_TRANSFERENCE))) && !(defender->info.flags & (MONST_INANIMATE | MONST_INVULNERABLE))) { - + transferenceAmount = min(damage, defender->currentHP); // Maximum transferred damage can't exceed the victim's remaining health. - + if (attacker == &player) { transferenceAmount = transferenceAmount * rogue.transference / 20; if (transferenceAmount == 0) { @@ -1510,18 +1548,18 @@ boolean inflictDamage(creature *attacker, creature *defender, } else { transferenceAmount = transferenceAmount * 9 / 10; // enemies get 90% recovery rate, deal with it } - + attacker->currentHP += transferenceAmount; - + if (attacker == &player && player.currentHP <= 0) { gameOver("Drained by a cursed ring", true); return false; } } - + if (defender->currentHP <= damage) { // killed - anyoneWantABite(defender); killCreature(defender, false); + anyoneWantABite(defender); killed = true; } else { // survived if (damage < 0 && defender->currentHP - damage > defender->info.maxHP) { @@ -1532,18 +1570,18 @@ boolean inflictDamage(creature *attacker, creature *defender, rogue.featRecord[FEAT_INDOMITABLE] = false; } } - + if (defender != &player && defender->creatureState != MONSTER_ALLY && defender->info.flags & MONST_FLEES_NEAR_DEATH && defender->info.maxHP / 4 >= defender->currentHP) { - + defender->creatureState = MONSTER_FLEEING; } if (flashColor && damage > 0) { flashMonster(defender, flashColor, MIN_FLASH_STRENGTH + (100 - MIN_FLASH_STRENGTH) * damage / defender->info.maxHP); } } - + refreshSideBar(-1, -1, false); return killed; } @@ -1563,7 +1601,7 @@ void addPoison(creature *monst, short durationIncrement, short concentrationIncr } monst->status[STATUS_POISONED] += durationIncrement; monst->maxStatus[STATUS_POISONED] = monst->info.maxHP / monst->poisonAmount; - + if (canSeeMonster(monst)) { flashMonster(monst, &poisonColor, 100); } @@ -1571,30 +1609,30 @@ void addPoison(creature *monst, short durationIncrement, short concentrationIncr } -// Removes the decedent from the screen and from the monster chain; inserts it into the graveyard chain; does NOT free the memory. -// Or, if the decedent is a player ally at the moment of death, insert it into the purgatory chain for possible future resurrection. +// Marks the decedent as dying, but does not remove it from the monster chain to avoid iterator invalidation; +// that is done in `removeDeadMonsters`. // Use "administrativeDeath" if the monster is being deleted for administrative purposes, as opposed to dying as a result of physical actions. // AdministrativeDeath means the monster simply disappears, with no messages, dropped item, DFs or other effect. void killCreature(creature *decedent, boolean administrativeDeath) { short x, y; - char monstName[DCOLS], buf[DCOLS]; - - if (decedent->bookkeepingFlags & MB_IS_DYING) { + char monstName[DCOLS], buf[DCOLS * 3]; + + if (decedent->bookkeepingFlags & (MB_IS_DYING | MB_HAS_DIED)) { // monster has already been killed; let's avoid overkill return; } - + if (decedent != &player) { decedent->bookkeepingFlags |= MB_IS_DYING; } - + if (rogue.lastTarget == decedent) { rogue.lastTarget = NULL; } if (rogue.yendorWarden == decedent) { rogue.yendorWarden = NULL; } - + if (decedent->carriedItem) { if (administrativeDeath) { deleteItem(decedent->carriedItem); @@ -1603,18 +1641,19 @@ void killCreature(creature *decedent, boolean administrativeDeath) { makeMonsterDropItem(decedent); } } - - if (!administrativeDeath && (decedent->info.abilityFlags & MA_DF_ON_DEATH)) { - spawnDungeonFeature(decedent->xLoc, decedent->yLoc, &dungeonFeatureCatalog[decedent->info.DFType], true, false); - + + if (!administrativeDeath && (decedent->info.abilityFlags & MA_DF_ON_DEATH) + && !(decedent->bookkeepingFlags & MB_IS_FALLING)) { + spawnDungeonFeature(decedent->loc.x, decedent->loc.y, &dungeonFeatureCatalog[decedent->info.DFType], true, false); + if (monsterText[decedent->info.monsterID].DFMessage[0] && canSeeMonster(decedent)) { monsterName(monstName, decedent, true); - sprintf(buf, "%s %s", monstName, monsterText[decedent->info.monsterID].DFMessage); + snprintf(buf, DCOLS * 3, "%s %s", monstName, monsterText[decedent->info.monsterID].DFMessage); resolvePronounEscapes(buf, decedent); - message(buf, false); + message(buf, 0); } } - + if (decedent == &player) { // the player died // game over handled elsewhere } else { @@ -1624,51 +1663,45 @@ void killCreature(creature *decedent, boolean administrativeDeath) { && !(decedent->info.flags & MONST_INANIMATE) && !(decedent->bookkeepingFlags & MB_BOUND_TO_LEADER) && !decedent->carriedMonster) { - - messageWithColor("you feel a sense of loss.", &badMessageColor, false); + + messageWithColor("you feel a sense of loss.", &badMessageColor, 0); } - x = decedent->xLoc; - y = decedent->yLoc; + x = decedent->loc.x; + y = decedent->loc.y; if (decedent->bookkeepingFlags & MB_IS_DORMANT) { pmap[x][y].flags &= ~HAS_DORMANT_MONSTER; } else { pmap[x][y].flags &= ~HAS_MONSTER; } - removeMonsterFromChain(decedent, dormantMonsters); - removeMonsterFromChain(decedent, monsters); - - if (decedent->leader == &player - && !(decedent->info.flags & MONST_INANIMATE) - && (decedent->bookkeepingFlags & MB_HAS_SOUL) - && !administrativeDeath) { - - decedent->nextCreature = purgatory->nextCreature; - purgatory->nextCreature = decedent; - } else { - decedent->nextCreature = graveyard->nextCreature; - graveyard->nextCreature = decedent; + + // This must be done at the same time as removing the HAS_MONSTER flag, or game state might + // end up inconsistent. + decedent->bookkeepingFlags |= MB_HAS_DIED; + if (administrativeDeath) { + decedent->bookkeepingFlags |= MB_ADMINISTRATIVE_DEATH; } - + if (!administrativeDeath && !(decedent->bookkeepingFlags & MB_IS_DORMANT)) { // Was there another monster inside? if (decedent->carriedMonster) { // Insert it into the chain. - decedent->carriedMonster->nextCreature = monsters->nextCreature; - monsters->nextCreature = decedent->carriedMonster; - decedent->carriedMonster->xLoc = x; - decedent->carriedMonster->yLoc = y; - decedent->carriedMonster->ticksUntilTurn = 200; + creature *carriedMonster = decedent->carriedMonster; + decedent->carriedMonster = NULL; + prependCreature(monsters, carriedMonster); + + carriedMonster->loc.x = x; + carriedMonster->loc.y = y; + carriedMonster->ticksUntilTurn = 200; pmap[x][y].flags |= HAS_MONSTER; - fadeInMonster(decedent->carriedMonster); - - if (canSeeMonster(decedent->carriedMonster)) { - monsterName(monstName, decedent->carriedMonster, true); + fadeInMonster(carriedMonster); + + if (canSeeMonster(carriedMonster)) { + monsterName(monstName, carriedMonster, true); sprintf(buf, "%s appears", monstName); combatMessage(buf, NULL); } - - applyInstantTileEffectsToCreature(decedent->carriedMonster); - decedent->carriedMonster = NULL; + + applyInstantTileEffectsToCreature(carriedMonster); } refreshDungeonCell(x, y); } @@ -1680,43 +1713,26 @@ void killCreature(creature *decedent, boolean administrativeDeath) { } } -void buildHitList(creature **hitList, - const creature *attacker, creature *defender, - const boolean penetrate, const boolean sweep) { +void buildHitList(creature **hitList, const creature *attacker, creature *defender, const boolean sweep) { short i, x, y, newX, newY, newestX, newestY; enum directions dir, newDir; - - x = attacker->xLoc; - y = attacker->yLoc; - newX = defender->xLoc; - newY = defender->yLoc; - + + x = attacker->loc.x; + y = attacker->loc.y; + newX = defender->loc.x; + newY = defender->loc.y; + dir = NO_DIRECTION; for (i = 0; i < DIRECTION_COUNT; i++) { if (nbDirs[i][0] == newX - x && nbDirs[i][1] == newY - y) { - + dir = i; break; } } - - if (penetrate && dir != NO_DIRECTION) { - hitList[0] = defender; - newestX = newX + nbDirs[dir][0]; - newestY = newY + nbDirs[dir][1]; - if (coordinatesAreInMap(newestX, newestY) && (pmap[newestX][newestY].flags & HAS_MONSTER)) { - defender = monsterAtLoc(newestX, newestY); - if (defender - && monsterWillAttackTarget(attacker, defender) - && (!cellHasTerrainFlag(defender->xLoc, defender->yLoc, T_OBSTRUCTS_PASSABILITY) || (defender->info.flags & MONST_ATTACKABLE_THRU_WALLS))) { - - // Attack the outermost monster first, so that spears of force can potentially send both of them flying. - hitList[1] = hitList[0]; - hitList[0] = defender; - } - } - } else if (sweep) { + + if (sweep) { if (dir == NO_DIRECTION) { dir = UP; // Just pick one. } @@ -1728,8 +1744,8 @@ void buildHitList(creature **hitList, defender = monsterAtLoc(newestX, newestY); if (defender && monsterWillAttackTarget(attacker, defender) - && (!cellHasTerrainFlag(defender->xLoc, defender->yLoc, T_OBSTRUCTS_PASSABILITY) || (defender->info.flags & MONST_ATTACKABLE_THRU_WALLS))) { - + && (!cellHasTerrainFlag(defender->loc.x, defender->loc.y, T_OBSTRUCTS_PASSABILITY) || (defender->info.flags & MONST_ATTACKABLE_THRU_WALLS))) { + hitList[i] = defender; } } @@ -1738,61 +1754,3 @@ void buildHitList(creature **hitList, hitList[0] = defender; } } - -// Basically runs a simplified deterministic melee combat simulation against a hypothetical -// monster with infinite HP (the dummy) and returns the amount of damage the tested -// monster deals before succumbing. Takes into account various environmental factors -// (e.g. current status effects). -short monsterPower(const creature *theMonst) { - short damageDealt[2] = {0, 0}; - short statuses[NUMBER_OF_STATUS_EFFECTS]; - short ticksTillTurn[2] = {0, 0}, speed; - short i, k; - double damagePerHit[2]; - double hitChance[2]; - - // [0] is the tested monster and [1] is the dummy. - // damageDealt measures how much damage each contestant has inflicted. - - for (i=0; istatus[i]; - } - - damagePerHit[0] = (theMonst->info.damage.lowerBound + theMonst->info.damage.upperBound) * monsterDamageAdjustmentAmount(theMonst) / 2; - damagePerHit[1] = 10; - hitChance[0] = monsterAccuracyAdjusted(theMonst) * pow(DEFENSE_FACTOR, 100); // Assumes the dummy has 100 armor. - hitChance[1] = monsterAccuracyAdjusted(theMonst) * pow(DEFENSE_FACTOR, theMonst->info.defense); - - while (damageDealt[1] < theMonst->currentHP) { // Loop until the dummy kills the monster in the simulation. - for (k=0; k<=1; k++) { // k is whose turn it is - - if (k==0) { // monster - speed = theMonst->info.attackSpeed; - if (statuses[STATUS_POISONED]) { - damageDealt[1] += theMonst->poisonAmount; // dummy gets credit for poison - } - if (statuses[STATUS_HASTED]) { - speed /= 2; - } - if (statuses[STATUS_SLOWED]) { - speed *= 2; - } - for (i=0; i 0) { - statuses[i]--; - } - } - } - - while (ticksTillTurn[k] <= 0) { - if (k == 1 || !statuses[STATUS_PARALYZED]) { - damageDealt[k] += damagePerHit[k] * hitChance[k]; - } - ticksTillTurn[k] += k ? 100 : speed; - } - ticksTillTurn[k] -= 100; - } - } - - return damageDealt[0]; -} diff --git a/src/brogue/Dijkstra.c b/src/brogue/Dijkstra.c index 9a6535f..a413a78 100644 --- a/src/brogue/Dijkstra.c +++ b/src/brogue/Dijkstra.c @@ -3,7 +3,7 @@ * Brogue * * Copyright 2012. All rights reserved. - * + * * This file is part of Brogue. * * This program is free software: you can redistribute it and/or modify @@ -26,39 +26,34 @@ #include "Rogue.h" #include "IncludeGlobals.h" -struct pdsLink { +typedef struct pdsLink { short distance; short cost; - pdsLink *left, *right; -}; - -struct pdsMap { - boolean eightWays; + struct pdsLink *left; + struct pdsLink *right; +} pdsLink; +typedef struct pdsMap { pdsLink front; pdsLink links[DCOLS * DROWS]; -}; +} pdsMap; -void pdsUpdate(pdsMap *map) { - short dir, dirs; - pdsLink *left = NULL, *right = NULL, *link = NULL; - - dirs = map->eightWays ? 8 : 4; +static void pdsUpdate(pdsMap *map, boolean useDiagonals) { + short dirs = useDiagonals ? 8 : 4; pdsLink *head = map->front.right; map->front.right = NULL; while (head != NULL) { - for (dir = 0; dir < dirs; dir++) { - link = head + (nbDirs[dir][0] + DCOLS * nbDirs[dir][1]); + for (short dir = 0; dir < dirs; dir++) { + pdsLink *link = head + (nbDirs[dir][0] + DCOLS * nbDirs[dir][1]); if (link < map->links || link >= map->links + DCOLS * DROWS) continue; // verify passability if (link->cost < 0) continue; if (dir >= 4) { - pdsLink *way1, *way2; - way1 = head + nbDirs[dir][0]; - way2 = head + DCOLS * nbDirs[dir][1]; + pdsLink *way1 = head + nbDirs[dir][0]; + pdsLink *way2 = head + DCOLS * nbDirs[dir][1]; if (way1->cost == PDS_OBSTRUCTION || way2->cost == PDS_OBSTRUCTION) continue; } @@ -70,9 +65,9 @@ void pdsUpdate(pdsMap *map) { if (link->right != NULL) link->right->left = link->left; if (link->left != NULL) link->left->right = link->right; - - left = head; - right = head->right; + + pdsLink *left = head; + pdsLink *right = head->right; while (right != NULL && right->distance < link->distance) { left = right; right = right->right; @@ -84,7 +79,7 @@ void pdsUpdate(pdsMap *map) { } } - right = head->right; + pdsLink *right = head->right; head->left = NULL; head->right = NULL; @@ -93,38 +88,28 @@ void pdsUpdate(pdsMap *map) { } } -void pdsClear(pdsMap *map, short maxDistance, boolean eightWays) { - short i; - - map->eightWays = eightWays; - +static void pdsClear(pdsMap *map, short maxDistance) { map->front.right = NULL; - for (i=0; i < DCOLS*DROWS; i++) { + for (int i=0; i < DCOLS*DROWS; i++) { map->links[i].distance = maxDistance; - map->links[i].left = map->links[i].right = NULL; + map->links[i].left = NULL; + map->links[i].right = NULL; } } -short pdsGetDistance(pdsMap *map, short x, short y) { - pdsUpdate(map); - return PDS_CELL(map, x, y)->distance; -} - -void pdsSetDistance(pdsMap *map, short x, short y, short distance) { - pdsLink *left, *right, *link; - +static void pdsSetDistance(pdsMap *map, short x, short y, short distance) { if (x > 0 && y > 0 && x < DCOLS - 1 && y < DROWS - 1) { - link = PDS_CELL(map, x, y); + pdsLink *link = PDS_CELL(map, x, y); if (link->distance > distance) { link->distance = distance; if (link->right != NULL) link->right->left = link->left; if (link->left != NULL) link->left->right = link->right; - left = &map->front; - right = map->front.right; - + pdsLink *left = &map->front; + pdsLink *right = map->front.right; + while (right != NULL && right->distance < link->distance) { left = right; right = right->right; @@ -138,32 +123,13 @@ void pdsSetDistance(pdsMap *map, short x, short y, short distance) { } } -void pdsSetCosts(pdsMap *map, short **costMap) { - short i, j; - - for (i=0; icost = costMap[i][j]; - } else { - PDS_CELL(map, i, j)->cost = PDS_FORBIDDEN; - } - } - } -} - -void pdsBatchInput(pdsMap *map, short **distanceMap, short **costMap, short maxDistance, boolean eightWays) { - short i, j; - pdsLink *left, *right; - - map->eightWays = eightWays; - - left = NULL; - right = NULL; +static void pdsBatchInput(pdsMap *map, short **distanceMap, short **costMap, short maxDistance) { + pdsLink *left = NULL; + pdsLink *right = NULL; map->front.right = NULL; - for (i=0; idistance; } } } -void pdsInvalidate(pdsMap *map, short maxDistance) { - pdsBatchInput(map, NULL, NULL, maxDistance, map->eightWays); -} - void dijkstraScan(short **distanceMap, short **costMap, boolean useDiagonals) { static pdsMap map; - pdsBatchInput(&map, distanceMap, costMap, 30000, useDiagonals); - pdsBatchOutput(&map, distanceMap); -} - -void calculateDistances(short **distanceMap, - short destinationX, short destinationY, - unsigned long blockingTerrainFlags, - creature *traveler, - boolean canUseSecretDoors, - boolean eightWays) { - creature *monst; - static pdsMap map; - - short i, j; - - for (i=0; iinfo.flags & (MONST_IMMUNE_TO_WEAPONS | MONST_INVULNERABLE)) - && (monst->info.flags & (MONST_IMMOBILE | MONST_GETS_TURN_ON_ACTIVATION))) { - - // Always avoid damage-immune stationary monsters. - cost = PDS_FORBIDDEN; - } else if (canUseSecretDoors - && cellHasTMFlag(i, j, TM_IS_SECRET) - && cellHasTerrainFlag(i, j, T_OBSTRUCTS_PASSABILITY) - && !(discoveredTerrainFlagsAtLoc(i, j) & T_OBSTRUCTS_PASSABILITY)) { - - cost = 1; - } else if (cellHasTerrainFlag(i, j, T_OBSTRUCTS_PASSABILITY) - || (traveler && traveler == &player && !(pmap[i][j].flags & (DISCOVERED | MAGIC_MAPPED)))) { - - cost = cellHasTerrainFlag(i, j, T_OBSTRUCTS_DIAGONAL_MOVEMENT) ? PDS_OBSTRUCTION : PDS_FORBIDDEN; - } else if ((traveler && monsterAvoids(traveler, i, j)) || cellHasTerrainFlag(i, j, blockingTerrainFlags)) { - cost = PDS_FORBIDDEN; - } else { - cost = 1; - } - - PDS_CELL(&map, i, j)->cost = cost; - } - } - - pdsClear(&map, 30000, eightWays); - pdsSetDistance(&map, destinationX, destinationY, 0); - pdsBatchOutput(&map, distanceMap); + pdsBatchInput(&map, distanceMap, costMap, 30000); + pdsBatchOutput(&map, distanceMap, useDiagonals); } /* BrogueBot addition. Fills costMap based on known dungeon info only. */ @@ -338,10 +252,53 @@ void calculateKnownDistances (short **distanceMap, freeGrid(costMap); } +void calculateDistances(short **distanceMap, + short destinationX, short destinationY, + unsigned long blockingTerrainFlags, + creature *traveler, + boolean canUseSecretDoors, + boolean eightWays) { + static pdsMap map; + + for (int i=0; iinfo.flags & (MONST_IMMUNE_TO_WEAPONS | MONST_INVULNERABLE)) + && (monst->info.flags & (MONST_IMMOBILE | MONST_GETS_TURN_ON_ACTIVATION))) { + + // Always avoid damage-immune stationary monsters. + cost = PDS_FORBIDDEN; + } else if (canUseSecretDoors + && cellHasTMFlag(i, j, TM_IS_SECRET) + && cellHasTerrainFlag(i, j, T_OBSTRUCTS_PASSABILITY) + && !(discoveredTerrainFlagsAtLoc(i, j) & T_OBSTRUCTS_PASSABILITY)) { + + cost = 1; + } else if (cellHasTerrainFlag(i, j, T_OBSTRUCTS_PASSABILITY) + || (traveler && traveler == &player && !(pmap[i][j].flags & (DISCOVERED | MAGIC_MAPPED)))) { + + cost = cellHasTerrainFlag(i, j, T_OBSTRUCTS_DIAGONAL_MOVEMENT) ? PDS_OBSTRUCTION : PDS_FORBIDDEN; + } else if ((traveler && monsterAvoids(traveler, i, j)) || cellHasTerrainFlag(i, j, blockingTerrainFlags)) { + cost = PDS_FORBIDDEN; + } else { + cost = 1; + } + + PDS_CELL(&map, i, j)->cost = cost; + } + } + + pdsClear(&map, 30000); + pdsSetDistance(&map, destinationX, destinationY, 0); + pdsBatchOutput(&map, distanceMap, eightWays); +} + short pathingDistance(short x1, short y1, short x2, short y2, unsigned long blockingTerrainFlags) { - short retval, **distanceMap = allocGrid(); + short **distanceMap = allocGrid(); calculateDistances(distanceMap, x2, y2, blockingTerrainFlags, NULL, true, true); - retval = distanceMap[x1][y1]; + short retval = distanceMap[x1][y1]; freeGrid(distanceMap); return retval; } diff --git a/src/brogue/Globals.c b/src/brogue/Globals.c index 5cd3113..afaed2f 100644 --- a/src/brogue/Globals.c +++ b/src/brogue/Globals.c @@ -4,7 +4,7 @@ * * Created by Brian Walker on 1/10/09. * Copyright 2012. All rights reserved. - * + * * This file is part of Brogue. * * This program is free software: you can redistribute it and/or modify @@ -37,19 +37,18 @@ short numberOfWaypoints; levelData *levels; creature player; playerCharacter rogue; -creature *monsters; -creature *dormantMonsters; -creature *graveyard; -creature *purgatory; +creatureList *monsters; +creatureList *dormantMonsters; +creatureList purgatory; item *floorItems; item *packItems; item *monsterItemsHopper; char displayedMessage[MESSAGE_LINES][COLS*2]; -boolean messageConfirmed[MESSAGE_LINES]; +short messagesUnconfirmed; char combatText[COLS * 2]; short messageArchivePosition; -char messageArchive[MESSAGE_ARCHIVE_LINES][COLS*2]; +archivedMessage messageArchive[MESSAGE_ARCHIVE_ENTRIES]; char currentFilePath[BROGUE_FILENAME_MAX]; @@ -67,9 +66,8 @@ unsigned long lengthOfPlaybackFile; unsigned long recordingLocation; unsigned long maxLevelChanges; char annotationPathname[BROGUE_FILENAME_MAX]; // pathname of annotation file -unsigned long previousGameSeed; +uint64_t previousGameSeed; -#pragma mark Colors // Red Green Blue RedRand GreenRand BlueRand Rand Dances? // basic colors const color white = {100, 100, 100, 0, 0, 0, 0, false}; @@ -92,6 +90,7 @@ const color darkBlue = {0, 0, 50, 0, 0, const color darkTurquoise = {0, 40, 65, 0, 0, 0, 0, false}; const color lightBlue = {40, 40, 100, 0, 0, 0, 0, false}; const color pink = {100, 60, 66, 0, 0, 0, 0, false}; +const color darkPink = {50, 30, 33, 0, 0, 0, 0, false}; const color red = {100, 0, 0, 0, 0, 0, 0, false}; const color darkRed = {50, 0, 0, 0, 0, 0, 0, false}; const color tanColor = {80, 67, 15, 0, 0, 0, 0, false}; @@ -261,6 +260,7 @@ const color spectralBladeLightColor ={40, 0, 230, 0, 0, const color ectoplasmLightColor = {23, 10, 28, 13, 0, 13, 3, false}; const color explosionColor = {10, 8, 2, 0, 2, 2, 0, true}; const color explosiveAuraColor = {2000, 0, -1000, 200, 200, 0, 0, true}; +const color sacrificeTargetColor = {100, -100, -300, 0, 100, 100, 0, true}; const color dartFlashColor = {500, 500, 500, 0, 2, 2, 0, true}; const color lichLightColor = {-50, 80, 30, 0, 0, 20, 0, true}; const color forceFieldLightColor = {10, 10, 10, 0, 50, 50, 0, true}; @@ -356,8 +356,6 @@ const color flameTitleColor = {0, 0, 0, 9, 9, 15, 0, true}; // *pale blue** //const color flameTitleColor = {0, 0, 0, 15, 15, 9, 0, true}; // pale yellow //const color flameTitleColor = {0, 0, 0, 15, 9, 15, 0, true}; // pale purple -#pragma mark Dynamic color references - const color *dynamicColors[NUMBER_DYNAMIC_COLORS][3] = { // used color shallow color deep color {&minersLightColor, &minersLightStartColor, &minersLightEndColor}, @@ -368,8 +366,6 @@ const color *dynamicColors[NUMBER_DYNAMIC_COLORS][3] = { {&chasmEdgeBackColor, &chasmEdgeBackColorStart, &chasmEdgeBackColorEnd}, }; -#pragma mark Autogenerator definitions - const autoGenerator autoGeneratorCatalog[NUMBER_AUTOGENERATORS] = { // terrain layer DF Machine reqDungeon reqLiquid >Depth