Putting the 'role' back in role-playing games since 2002.
Donate to Codex
Good Old Games
  • Welcome to rpgcodex.net, a site dedicated to discussing computer based role-playing games in a free and open fashion. We're less strict than other forums, but please refer to the rules.

    "This message is awaiting moderator approval": All new users must pass through our moderation queue before they will be able to post normally. Until your account has "passed" your posts will only be visible to yourself (and moderators) until they are approved. Give us a week to get around to approving / deleting / ignoring your mundane opinion on crap before hassling us about it. Once you have passed the moderation period (think of it as a test), you will be able to post normally, just like all the other retards.

Realms of Antiquity: The Shattered Crown - Ultima-like retro RPG

rikkles

Arcane
Developer
Joined
Aug 22, 2017
Messages
138
You're not making any sense. Whether a game is linear or open-world matters none in terms of difficulty, Skyrim being the best example. Actually, games with heavy focus on exploration tend to be much easier since you can sequence break or get powerful items early.
Also, I'm obviously asking about combat, not the puzzles.
Imagine getting in a dungeon with a definite and finite amount of items and spells. And you need to get through it, with tons of fights and boss at the end who’ll soak in as much damage as you can throw at it. You’re going to need to manage your resources well, plan your tactics, and ensure your team is properly balanced.
Now imagine this dungeon being 3 levels of 64x64 tiles and sometimes much much more.
 

rikkles

Arcane
Developer
Joined
Aug 22, 2017
Messages
138
The reason I said it’s not difficult is that when you learn about each region you’ll develop tactics that will consistently allow you to overcome the dangers. Compared to a sadistic Wiz IV or old style crawlers that thrive on total randomness and one-shot death because you got unlucky, this ain’t it.

PS: I’m the guy who wins it solo for the fun of it so my idea of easy may not be everyone’s. That said, the game is consistent.
 

Grauken

Gourd vibes only
Patron
Joined
Mar 22, 2013
Messages
12,803
The main difficulty is the actual scale of the game.
The amount of exploration is what makes it tough.
You're not making any sense. Whether a game is linear or open-world matters none in terms of difficulty, Skyrim being the best example. Actually, games with heavy focus on exploration tend to be much easier since you can sequence break or get powerful items early.
Also, I'm obviously asking about combat, not the puzzles.

Skyrim is a modern game where resource management is rarely a source of the difficulty. RoA is more old-school, going out into the world is like going into a dungeon in early Wizardries, is not the individual combat encounters that matter (though they can be challenging as well) but the sum of all of them together that pose a difficulty.
 

Arthandas

Prophet
Joined
Apr 21, 2015
Messages
1,385
Imagine getting in a dungeon with a definite and finite amount of items and spells. And you need to get through it, with tons of fights and boss at the end who’ll soak in as much damage as you can throw at it. You’re going to need to manage your resources well, plan your tactics, and ensure your team is properly balanced.
Seems like typical blobber (which is great). Are you telling me RoA is like that? Because it doesn't look like it.
Is there a demo?
 

rikkles

Arcane
Developer
Joined
Aug 22, 2017
Messages
138
i don’t think there’s a demo but there are videos of gameplay. It’s Ultima-like in its visuals, but blobber-like in its gameplay. It’s very very good. Balanced, fun and logical.
And it can be played with Grid Cartographer for mapping.
 

KeighnMcDeath

RPG Codex Boomer
Joined
Nov 23, 2016
Messages
13,062
Watching parts of the first video here is really all I need to see to buy on itchio (hell yeah for NON-STEAM options!). Fantastic effects, sound, music, and more on such old hardware (or emulator-ware). I'm an old school lover so its great. I need to reread the manual for a third or fourth time but I guess you search or look at an object by [BUMPING INTO IT?]. That's about the only unclear thing i kept pondering. And I guess not everything says some (like walls)? No (invalid move.. BLOCKED) messages?
Ultima
Wasteland
Xyphus
Shard of spring... This is and has been an incline imho. Legends I & II were some of the last games I perused for the system. (Probably mentioned that before).

I did glance at that boss battle... OUCH! If you want to avoid spoilers don't watch these or just watch like the first fight. Or... read the nanual, command card (print em out) and GO. The emulator loooks to load fast.

It looks GOLDEN.

I'd like to see a vid with the cartographer running.
 

Denim Destroyer

Learned
Joined
Mar 20, 2021
Messages
433
Location
Moonglow, Britannia
Looks like the game got a small update. Mostly quality of life effects related to attack notifications but still welcome changes.
https://store.steampowered.com/news/app/1539750/view/3082145182485986005

Build 4.17.040 released

Changelist:
Bugs
  • Fixed a bug where FX of special attacks appeared on monsters who were immune to them.
  • Fixed a bug where the party avatar didn't change when transitioning from overworld to town.

Features
  • Olaf's Outfitters in Skalsburg now buys items as well as sells them.
  • Updated attack notifications:
    • If damage is zero, instead of missing you are notified "Target takes no damage".
    • If the attack is resisted, you are notified "Target resists" instead of "no effect".
    • If immune to the given attack, you are notified "Target is immune".
    • Cleaned up display of notifications to avoid "blinky" effects with the text.

  • Adjusted damages for several one-handed maces up slightly.
  • Adjusted spawn clock to be longer on average to reduce random spawns.
  • Increased and added money drops for several monsters, particularly bosses.
  • Increased the power of traps on chests found after battle.
 
Last edited by a moderator:

lecrop

Educated
Joined
Mar 31, 2014
Messages
22
Priory quest at Weymoor, does anyone know if it is possible to enter the treasure room?

mbdO9yF.jpg
 

KeighnMcDeath

RPG Codex Boomer
Joined
Nov 23, 2016
Messages
13,062
I saw chests and your name made me think it was Leprechaunis. (gimme the gold for me pot).
lep-2-2.jpg
 

V_K

Arcane
Joined
Nov 3, 2013
Messages
7,714
Location
at a Nowhere near you
Question: the manual says that Reason contributes to Stamina, yet my very reasonable wizard seems to have less Stamina than my very unreasonable fighter. Bug or are there other mechanics at play?
 

KeighnMcDeath

RPG Codex Boomer
Joined
Nov 23, 2016
Messages
13,062
He's too unreasonable to know he's tired. Reason realizes it and wiz becomes depressed and tired. ... maybe.


Perhaps Beastman can enlighten us on this matter.
 
Last edited:

adamantyr

Arcane
Developer
Joined
Apr 5, 2019
Messages
154
Location
Marysville, WA
Question: the manual says that Reason contributes to Stamina, yet my very reasonable wizard seems to have less Stamina than my very unreasonable fighter. Bug or are there other mechanics at play?

Agility and Reason both contribute to Stamina, so if your fighter has a high agility that would do it.
 

V_K

Arcane
Joined
Nov 3, 2013
Messages
7,714
Location
at a Nowhere near you
Question: the manual says that Reason contributes to Stamina, yet my very reasonable wizard seems to have less Stamina than my very unreasonable fighter. Bug or are there other mechanics at play?

Agility and Reason both contribute to Stamina, so if your fighter has a high agility that would do it.
Is there any technical reason why examining a spellbook cannot just give you the list of spells in it? It's rather annoying having to open the inventory, equip a book, close the inventory, open stats, list towards the spell list, realize the one you're looking for is not there, and do it all over again. The game's UI generally seems very fine, especially for a retro-PC title, but this particular operation seems to be not very well thought out.
Also, why can't you examine an equipped item?
 

adamantyr

Arcane
Developer
Joined
Apr 5, 2019
Messages
154
Location
Marysville, WA
Is there any technical reason why examining a spellbook cannot just give you the list of spells in it? It's rather annoying having to open the inventory, equip a book, close the inventory, open stats, list towards the spell list, realize the one you're looking for is not there, and do it all over again. The game's UI generally seems very fine, especially for a retro-PC title, but this particular operation seems to be not very well thought out.
Also, why can't you examine an equipped item?

With spellbooks, the examine option only allows for two lines of viewing, not nearly enough for a spellbook. That would require a completely different interface to display, which I didn't have room in the module to implement.

It's a fair criticism, and in a future game I'd make it displayable. The bigger ask was "why can't I view a spellbook BEFORE I buy it?" And same goes with examining items, why can't I examine items in the shop before I buy, etc. Again, doable in a future game, but too much work to redesign a lot of modules right now.

Examining an equipped item, on the other hand, is probably something I could do with only a couple bytes of changes. I will look into it.
 

adamantyr

Arcane
Developer
Joined
Apr 5, 2019
Messages
154
Location
Marysville, WA
Actually reduced the byte count of the module by 2, just had to move the examine block and change a screen reference for the "can't remove backpack is full". :) Look for a future release with the change.
 

Infinitron

I post news
Staff Member
Joined
Jan 28, 2011
Messages
97,506
Codex Year of the Donut Serpent in the Staglands Dead State Divinity: Original Sin Project: Eternity Torment: Tides of Numenera Wasteland 2 Shadorwun: Hong Kong Divinity: Original Sin 2 A Beautifully Desolate Campaign Pillars of Eternity 2: Deadfire Pathfinder: Kingmaker Pathfinder: Wrath I'm very into cock and ball torture I helped put crap in Monomyth
A belated part two: http://www.adamantyr.com/blog/index...crpg-part-2-maps-scrolling-and-line-of-sight/

Making a CRPG Part 2 – Maps, Scrolling and Line of Sight

In this, part 2, we will be looking at scrolling maps, a pivotal element of top-down 2D computer role play games of yore.

I think something that is lost on many modern gamers, who didn’t grow up in the 80’s. The majority of games on the early 8-bit systems were limited to a single screen of play. Really good games may have multiple screens but when you moved off the edge it would load a new screen.

hqdefault-300x225.jpg

It’s bigger on the inside!

So it was utterly FANTASTIC to see a game screen that was a view-port on a much larger world. When I first saw Ultima II, I was in total shock. There was (to my viewpoint) no limit to what could be beyond the borders of the screen! It both was thrilling and increased my curiosity and expectations of what the game could have.

A scrolling map was also well beyond what most BASIC languages could do in those days. Some were better than others at high-speed video display, but anything close to full screen was a challenge no matter the platform. So knowledge of assembly language was needed to implement one.

So how to render a map that is bigger than the screen? Let’s dive in…
Storing Maps
The first thing to figure out how you are encoding your maps internally. How much memory are you devoting internally to a map? Is the data compressed on disk and must be uncompressed when loaded? How many unique tiles can be present on a map? Are tiles global or specified for the map?

Early Ultima’s like II and III had 64×64 size maps. Both had less than a byte’s worth of tiles (64 and 128) so they would use up 4K of RAM to store the map, uncompressed and using a full byte per tile. Ultima IV uses 32×32 size maps and some clever coding to load a continuous world map. As you walk around, new 32×32 chunks are loaded. This creates some challenging edge cases (literally) when you approach corners and the game will need to load up to 3 new chunks to get the data needed. Ultima V goes to 16×16 chunks for the world map.

The issue with doing continuous map loads on early 8-bit systems is most of them aren’t that efficient with loading data continuously. Until hard drives came along, loading even a few kilobytes from floppy disk could take a second or so. And it got worse on systems like the Commodore 64 where the continuous music would suddenly get stuck on a single note as it loaded fresh data. My own experiments with a “big map” showed the problem, as every time I approached an edge it would take seconds to load fresh data.

So I decided for my own maps to just have singleton maps that are a maximum of 4K in size, and if you left them you’d just load a new one as a self-contained area. For tiles, I have 128, and multiple character sets. So for a world map, there is a “world” tile set that includes mountains and other features only found on world maps. The leftover bit is used as a lighting mechanic; it indicates if this tile is “lit” or not naturally on the map.

The nice thing about a map buffer is it can take any shape you want. Just because I have 4096 tiles doesn’t mean it’s automatically 64×64. By specifying a height and width parameter for each map, I can have them in many different sizes. In practice I found that 32×32 was a decent size for most towns and dungeons. 48×48 was nearly perfect, just big enough to have a lot of interesting areas. 64×64 was almost TOO big, there was a few cases where I split maps into multiple maps because a super large map wasn’t actually ideal, especially if they had a lot of mobs (mobile objects) on them.

Some game maps are compressed on disk so they take less room. With a lot of older CRPG’s this makes good sense; you don’t have a lot of unique tiles and you tend to have long horizontal stripes of them, which screams “compress me!” The most common form of compression is RLE, or run-length-encoding. RLE defines the data as either a singleton (one tile) or a count of whatever comes next of values to repeat. There’s usually also a terminator value as well to indicate map processing should end.

For example, if you have 64 unique tiles, the top two bits could be used to indicate a few different ideas:

  1. The top two bits are control bits. If both are 0, it’s a terminator for the map data. If 01, this is a singleton tile. If 10, it’s two consecutive tiles (or whatever the most common count of consecutive tiles is in your maps.) If 11, use the tile value as a count and that’s how many of the next tile to produce.
  2. The top two bits are count bits. Value 0 means it’s a terminator for the map data. Otherwise produce 1-3 of the given tile.
rle-example.png
Let’s look at an example, here is a 7×7 map, showing an island. Uncompressed, if you used a full byte per tile, it would take up 49 bytes.

With method #1 above, it would take two bytes for any length of greater than 2 to store. Crunching the numbers, it would take 25 bytes (including a terminator byte) to store. Almost 50% compression!

With method #2 above, while we are limited to a maximum of 3 repeated tiles, it actually comes in at 21 bytes, over 50% compression. Nice! And the algorithm to decompress is a little simpler as well.

Of course, this example only has two tile types, and the map is rather straight-forward. Any CRPG map is likely to have a lot richer of a data set. And there is a point where RLE won’t be as effective. My own maps utilize a lot of “two pair” tiles where they are the same type (grass, for instance) but are in fact different tiles. This breaks RLE, which expects a lot of the same tiles to be repeated in a horizontal line. For such maps, using a more complex pattern-oriented compression technique like LZW and Huffman makes more sense.

The main value of compression is to reduce disk size, though. Is disk size really a problem to solve? In the old days, the answer was unhesitatingly yes. Most 8-bit systems had 160-180K disks, and every disk was an expense to replicate and put in a box. Data compression saved money. In the modern era, though, with digital distribution and USB sticks that hold more memory than the entire production run of a computer system’s RAM added together, it’s not as big of a deal. Even retro enthusiasts these days tend towards modern storage solutions like emulated disk systems or even cartridges with megabytes of space available. So I figured, why bother?

Towards the end of my development work, I did consider that it would be better to have dynamic tiles. In other words, store a key list of the unique tiles used on the map, assign them unique values, then the map data itself using those values. It’s a nice idea, as each map then basically has it’s own unique tile set, and you wouldn’t have a lot of needless replication. But it adds a lot of overhead towards map loading, and would require a complicated editor for maps. Something to consider for the future…

Viewing Maps
Okay, so you have your map loaded in memory. How to get it on screen?

Well, the first thing is to determine the number of tiles you want to appear on the screen. Depending on how many pixels per tile, how large the screen, etc. And is your “avatar” character always at the center? If so, an odd number of tiles per side makes sense for balance.

Using a ‘view-port buffer’ is a best approach. Don’t try and pull each tile individually from your map data, use an in-between buffer to store it. Using map height and width and a position offset, it’s not hard to create a double loop to copy out map data to your view-port buffer. But what do you do when your view-port goes over the edge of the map?

Different games handle it different ways. If you want your map to just stop scrolling at edges, Gauntlet-style, that’s easy to do; you just make sure offsets never go past a certain value. This does create the sense of reaching a “map edge” though, and may remove the player a little from immersion.

Other games just have a default “overflow” tile that is used, and interacting with it will pop the character off the map. This also works but still clearly illustrates that you’ve reached a map edge.

For my own game, I had two versions. One is “repeat the edge tile” which takes whatever tile was along that edge and repeats it indefinitely. This creates a less obvious map edge. The other version is “wrap-around”, which creates a continuous map by wrapping to the other side. I use this one sparingly, usually for maps that are either “warped magic” in nature or in a more clever fashion to create non-square style caverns.

02-Craggy-Fjords-300x175.png

No wasted space!

One other feature I introduced with my own map system is slanted maps. Since map projection is up for interpretation, I can slant map data by row to make a more natural style map for coast lines that aren’t going in cardinal directions. Very useful, and very difficult to debug!

One additional task is to place mobs (mobile objects) on your map. This would be your monsters, points of interest, and other items that aren’t part of the static map. I store these in a separate data set, so they must be placed in the view-port area if they are visible. This puts a practical limit of how many mobs per map, since every extra calculation is costing you processing time.

So now we have our view-port, with our set of tiles. Now (finally) it’s time for line of sight!

Line Of Sight
Back in the early 90’s I tried to create the LOS algorithm in BASIC, using sine and cosine functions. I thought of it as I was flinging light out from the center and traversing a circle, and that if I struck a barrier that blocked line of sight, everything after it on the path would be dark.

So… it kind of worked. Except that even on a 11×11 size screen and going 1 degree at a time, it still didn’t cover all the squares. Plus given it was in BASIC it ran VERY slowly, taking ten minutes to complete.

Years later, I got a hold of the Ultima III LOS algorithm, and was able to see my error. I was thinking in reverse! What you do is trace a path from the tile you want to check LOS on and traverse back to the center.

los1-300x300.png

Diagonal then Cardinal

The algorithm is pretty simple:

  • Hide all tiles in the view-port, except the center tile, where your avatar is.
  • For each tile in the view-port, plot a path back to center. The original algorithm uses two arrays to achieve an offset. It moves diagonally towards the center until it hits a cardinal direction, then continues the rest of the way on the cardinal.
  • If at any point you encounter a “blocking” tile, move on to the next tile.
  • Otherwise, if you reach center, uncover the target tile and move to the next tile.
Simple indeed, but processor-intensive. Besides processing the view port in start-to-end order without taking the 2D nature of the data into account, it also ends up reprocessing a LOT of tiles unnecessarily. If a tile is blocked, then wouldn’t any tiles between it and the blocking tile also be blocked? And if so, why calculate those at all?

I introduced some optimizations to reduce unnecessary calculations and it worked pretty well. However, the LOS algorithm in Ultima III was rather heavy; a single tile creates a huge swath of diagonal shadow behind it. I noticed that Ultima IV on the PC seemed to have a better algorithm so I took a copy of the source from XU4 (sadly now an extinct project) to analyze.

los2-300x300.png

Cardinal then Diagonal

The algorithm is VERY different. It starts by tracing in the cardinal directions from the center outward. If it encounters a blocking tile, it blocks only the cardinal tiles behind it.

After the four cardinal plots, it then does four quadrant calculations, which start on the edge and go towards the center on a horizontal or vertical direction first then diagonal. This has the effect of not blocking so pervasively. It’s a much more efficient algorithm because it’s actually taking the structure of the view-port into consideration.

Light
los-light-300x300.png

Light Map

So, remember that light bit on the tiles? That’s used to determine dark and light areas. But, if the player has a light source going, how to determine if a square is lit or not?

I use a light map, which has concentric circles of numbers, matching the size of the view port. The center area is value 0, and slowly increases as it goes outward. A light source has a strength (or radius) value, which the light map is subtracted from. If the value is less than zero, it’s not lit. If it’s equal or greater, it’s lit.

The light map is always applied after LOS has been calculated. A square that’s already blocked remains blocked.

Elevation
WP_20130330_002-300x169.jpg
This was a late-development feature, which came about when I was out on a ridge one day looking over a magnificent set of waterfalls in the distance. I realized that despite being in a forest and having a deep valley of forest between us, I could still see the falls clearly. And that got me thinking, what about elevation and LOS?

The elevation map is a separate data set from the map’s data, as not every map uses elevation. The data is also stored in a compressed format (run-length encoding!) so it doesn’t take up much disk space. There are four levels of elevation, from 0 to 3.

The math is easy. Whatever elevation your avatar is at, everything below it is not a blocking tile. Everything at same level respects the LOS calculations. And everything above your level is considered a blocking tile automatically.

I mostly use elevation on world maps, but a few special maps use it. I think my favorite in this regard is a sewer dungeon, with both upper and lower levels. The biggest problem to solve with elevation was coming up with clear boundary delineation.

The Code
Below is ROA’s map view algorithm, in TMS9900 assembly. It uses multiple buffer maps for each stage and then combines them at the end for the finished mapview.

* Map Building Routine
* Extract map from map buffer into VMAP
BLDMAP MOV R11,*R10+
LI R0,MOBVIS
LI R1,32
BLDMP0 CLR *R0+ * Clear mob visibility array
DEC R1
JNE BLDMP0
LI R3,2
BLWP @PAGE1 * Set >2000 to page 2 (map buffer)
LI R3,1
BLWP @PAGE2 * Set >3000 to page 1 (elevation)
MOVB @SLANT,R1 * Get orientation into R1
SRL R1,8
MOV @DIRY(R1),R9 * Set R9 to -1 (left), 0 (none), or 1 (right)
MOV @DSLANT(R1),R8 * Set R8 to 0 (left), -6 (none), -12 (right)
MOV @Y,R1 * Get Y value into R1
MOV @X,R2 * Get X value into R2
AI R1,-6 * Set starting y position
A R8,R2 * Add slant start to x position
LI R3,13 * Row count
LI R4,13 * Column count
CLR R5 * buffer index
MOVB @EDGES,@EDGES * Check if edge or repeating map
JNE BLDMPW
* Build map with edge
BLDMPE MOV R1,R6 * Copy R1 to R6
MOV R2,R7 * Copy R2 to R7
BL @EDGCHK * Check if over edge
MOV R0,R0
JEQ BLDME1
* Edge correction
COC @W1,R0 * Vertical?
JNE EDGEM2
MOV R6,R6
JLT EDGEM1
MOV @VWIDTH,R6
DEC R6
JMP EDGEM2
EDGEM1 CLR R6
EDGEM2 COC @W2,R0 * Horizontal?
JNE BLDME1
MOV R7,R7
JLT EDGEM3
MOV @HWIDTH,R7
DEC R7
JMP BLDME1
EDGEM3 CLR R7
* Get tile
BLDME1 MOV R7,R0 * Copy R7 to R0
MPY @HWIDTH,R6 * Multiply Y by hortz width
A R0,R7 * Calculate map index into R7
MOVB @MAPBUF(R7),@VMAP(R5) * Copy tile
MOVB @MAPENV(R7),@EMAP(R5) * Copy elevation level
MOVB @VMAP(R5),@LMAP(R5) * Copy light level
SOCB @B128,@VMAP(R5) * Set high bit on active tile
SZCB @B127,@LMAP(R5) * Filter light array to top bit only
MOVB @B1,@LOSMAP(R5) * Set LOS map to blocked
INC R5 * Increment the buffer pointer
INC R2 * Increment the column index
DEC R4 * Decrement the window width count
JNE BLDMPE
INC R1 * Increment the row index
A R9,R8 * Add slant change to R8
MOV @X,R2 * Move X back into R2
A R8,R2 * Add slant to x position
LI R4,13 * Reset the window width to 13
DEC R3 * Decrement the window height count
JNE BLDMPE
JMP BLDMP2 * Jump to next routine
* Build map with wrapping
BLDMPW MOV R1,R6 * Copy R1 to R6
MOV R2,R7 * Copy R2 to R7
BL @EDGCHK * Check if over edge
MOV R0,R0
JEQ BLDMW1
* Wrap correction
COC @W1,R0 * Vertical?
JNE WRAPM2
MOV R6,R6
JLT WRAPM1
S @VWIDTH,R6
JMP WRAPM2
WRAPM1 A @VWIDTH,R6
WRAPM2 COC @W2,R0 * Horizontal?
JNE BLDMW1
MOV R7,R7
JLT WRAPM3
S @HWIDTH,R7
JMP BLDMW1
WRAPM3 A @HWIDTH,R7
* Get tile
BLDMW1 MOV R7,R0 * Copy R7 to R0
MPY @HWIDTH,R6 * Multiply Y by hortz width
A R0,R7 * Calculate map index into R7
MOVB @MAPBUF(R7),@VMAP(R5) * Copy tile
MOVB @MAPENV(R7),@EMAP(R5) * Copy elevation level
MOVB @VMAP(R5),@LMAP(R5) * Copy light level
SOCB @B128,@VMAP(R5) * Set high bit on active tile
SZCB @B127,@LMAP(R5) * Filter light array to top bit only
MOVB @B2,@LOSMAP(R5) * Set LOS map to blocked
INC R5 * Increment the buffer pointer
INC R2 * Increment the column index
DEC R4 * Decrement the window width count
JNE BLDMPW
INC R1 * Increment the row index
A R9,R8 * Add slant change to R8
MOV @X,R2 * Move X back into R2
A R8,R2 * Add slant to x position
LI R4,13 * Reset the window width to 13
DEC R3 * Decrement the window height count
JNE BLDMPW
* Retrieve data into state and sensing arrays
BLDMP2 MOVB @VMAP+84,@CTILE * Set current tile value
MOVB @EMAP+84,@CELEV * Set current elevation level
MOVB @B247,@VMAP+84 * Set player graphic for permissible space
MOVB @B1,@LOSMAP+84 * Set center of LOS map to visible
SETO @SURRND * Clear the surrounding tile contents
SETO @SURRND+2
SETO @SURRND+4
SETO @SURRND+6
MOVB @VMAP+97,@SURRND * Copy the down tile
MOVB @VMAP+83,@SURRND+2 * Copy the left tile
MOVB @VMAP+71,@SURRND+4 * Copy the up tile
MOVB @VMAP+85,@SURRND+6 * Copy the right tile
* Mob processing
LI R3,4
BLWP @PAGE2
CLR @WORK2 * Clear WORK2 (in map mob count)
MOV @MOBCNT,R0 * Copy total mob count to R0
JEQ BLDMP3 * If 0, skip to next phase
MOV @MOBADR,R1
CLR @WORK * Clear @WORK (Mob #)
LI R6,WORK2+2 * Set R6 to WORK2+2
BM2 MOVB *R1,R2 * Copy mob type into R2
JEQ BM2B * If zero, skip, no counter decrease
CB @B23,R2 * Check if inert
JEQ BM2B * If so, skip but decrease counter
JMP BM2C
BM2A AB @B1,@WORK
DEC R0 * Decrement mobs processed
JEQ BLDMP3 * If finished, move on
JMP BM2
BM2B AB @B1,@WORK
AI R1,8 * Go to next mob
DEC R0 * Decrement mobs processed
JEQ BLDMP3 * If finished, move on
JMP BM2
BM2C MOV *R1+,@MAPMOB * Get mob data
MOV *R1+,@MAPMOB+2
MOV *R1+,@MAPMOB+4
MOV *R1+,@MAPMOB+6
BLWP @MOBWIN * Calculate window positon
MOV R3,R3
JLT BM2A * Not visible, skip placement
INC @WORK2 * Increase mob count
MOV R3,*R6+ * Copy index to WORK2 array
MOVB @MAPMOB+1,*R6+ * Copy pattern to WORK2 array
MOVB @WORK,*R6+ * Copy mob index
MOVB @MAPMOB+1,@VMAP(R3) * Copy pattern to VMAP for LOS calculations
LI R4,4
LI R2,MOBSEN * Load mob sense data
BM2D C R3,*R2+ * Check if position is next to player
JNE BM2E
MOV *R2+,R5 * Get address into R5
MOVB @WORK,*R5 * Copy mob to state array
BM2E INCT R2
BM2F DEC R4 * Loop all four locations
JNE BM2D
JMP BM2A
* Update sense counter for traps/secrets
BLDMP3 CLR R1 * Clear R1 for sense counter
LI R0,SURRND+1 * Set R0 to SURRND array, mob area
LI R2,4 * Set R2 to 4 (4 directions)
BM3A CLR R3
MOVB *R0+,R3 * Copy mob # to R3
JLT BM3B * if negative, skip
SRL R3,5 * Make 8-step index
A @MOBADR,R3
MOVB *R3,R4 * Copy mob type to R4
SB @B16,R4 * Subtract 16 from mob ID
JLT BM3B * If less than zero, not a hidden mob
SRL R4,8 * Shift value to make index
MOVB @SENSEV(R4),R5 * Copy from character array
JEQ BM3B * If 0, skip
SOCB R5,R1 * Set bit
BM3B INC R0 * Increase to next tile position
DEC R2 * Decrement counter
JNE BM3A
MOVB R1,@SENSEC+1 * Copy R1 to SENSEC (Counter)
* Check party light level, map onto tiles
LGTMAP MOV @MAGEYE,R0 * Check if magic eye is active
JEQ LGT1
BL @MEVIEW * Fully open map
B @PCME4A
LGT1 MOVB @LIGHT,R0 * Check if map is fully lit
JEQ LGT1A * If so, skip to LOS algorithm
MOVB @PARTY+30,R0
ANDI R0,>2000 * Check for Radiant Pharos
JEQ LGT1B
LGT1A BL @MEVIEW * Fully open map
JMP LOS
LGT1B CLR R1 * Set buffer index
LI R5,169 * Set buffer counter
LGT2 MOVB @ALIGHT(R1),R2 * Copy window position light value into R2
SB @LGTLV+1,R2 * Subtract the current light strength from window value
JGT LGT3 * If greater, unlit
MOVB @B128,@LMAP(R1) * Otherwise, mark the tile lit
LGT3 INC R1
DEC R5
JNE LGT2
* Map line-of-sight on the map
LOS LI R8,4 * Number of directions to process
CLR R9 * Direction index
CLOS1 LI R7,6 * Count of tiles
LI R0,6 * Column position
LI R1,6 * Row position
CLOS2 CLR R12 * Set tile to copy to closed tile
BL @POSCLC * Calculate position
MOVB @LOSMAP(R3),R2
JEQ CLOS3
CI R3,84
JEQ CLOS2A
BL @CHKTLE * Fetch tile's opacity level, also check elevation
ANDI R5,>8000 * Check if a blocking tile
JNE CLOS3
CLOS2A LI R12,>0100 * Set tile to open
* Set tile
CLOS3 MOV @MAPLOS(R9),R2 * Copy direction vector index
A @DIRX(R2),R0 * Add direction vector to column
A @DIRY(R2),R1 * Add direction vector to row
BL @POSCLC * Get buffer index
MOVB R12,@LOSMAP(R3) * Copy visible/blocked value to buffer
DEC R7
JNE CLOS2 * Loop through path
INCT R9 * Change direction
DEC R8
JNE CLOS1 * Loop through rows
* Diagonal LOS
DLOS LI R8,4 * Number of directions to process
CLR R9 * Direction index
DLOS1 LI R6,6
LI R0,6
DLOS2 LI R7,6
LI R1,6
DLOS3 BL @POSCLC
* Collect four tile indices in FRAM array
MOV R3,@FRAM
MOV R0,@FRAM+8
MOV R1,@FRAM+10
MOV @MAPLOS+8(R9),R2
A @DIRX(R2),R0
A @DIRY(R2),R1
BL @POSCLC
MOV R3,@FRAM+2
MOV @FRAM+8,R0
MOV @FRAM+10,R1
MOV @MAPLOS+10(R9),R2
A @DIRX(R2),R0
A @DIRY(R2),R1
BL @POSCLC
MOV R3,@FRAM+4
MOV @MAPLOS+8(R9),R2
A @DIRX(R2),R0
A @DIRY(R2),R1
BL @POSCLC
MOV R3,@FRAM+6
MOV @FRAM+8,R0
MOV @FRAM+10,R1
* Check three surrounding tiles
CLR R12
MOV @W3,@FRAM+8
LI R2,FRAM
DLOS4 MOV *R2+,R3
MOVB @LOSMAP(R3),R4
JEQ DLOS5
BL @CHKTLE
ANDI R5,>8000
JEQ DLOS6
DLOS5 DEC @FRAM+8
JNE DLOS4
JMP DLOS7
DLOS6 LI R12,>0100
* Set tile
DLOS7 MOV @FRAM+6,R3
MOVB R12,@LOSMAP(R3)
MOV @MAPLOS+8(R9),R2
A @DIRY(R2),R1
DEC R7
JNE DLOS3
MOV @MAPLOS+10(R9),R2
A @DIRX(R2),R0
DEC R6
JNE DLOS2
AI R9,4
DEC R8
JNE DLOS1
* Final opening of permitted space
PCMEND CLR R1 * Set buffer index
LI R0,169 * Set buffer counter
MOVB @CELEV,R2 * Copy elevation to R2
PCME1 MOVB @LOSMAP(R1),R3 * Check LOS
JEQ PCME3
MOVB @VMAP(R1),@LMAP(R1) * Set the tile onto the map
JMP PCME4
PCME3 MOVB @SPACE,@LMAP(R1) * Make the tile black (invisible)
PCME4 INC R1
DEC R0
JNE PCME1
* Place visible mobs
PCME4A MOV @WORK2,R0 * Check mob count
JEQ PCME7 * If zero, skip
LI R1,WORK2+2
PCME5 MOV *R1+,R2 * Get mob index
MOV *R1+,R3 * Get pattern
CB @LMAP(R2),@SPACE * Is the space blacked out?
JEQ PCME6
MOVB R3,@LMAP(R2) * Set mob on map
ANDI R3,>00FF * Get mob #
MOVB @B1,@MOBVIS(R3) * Set mob visibility to 1
PCME6 DEC R0 * Decrement count
JNE PCME5
PCME7 LI R0,>F700
SB @BOAT,R0
MOVB R0,@LMAP+84 * Copy the party icon to the center
B @SUBRET

* Magic eye/Pharos view
MEVIEW LI R0,169 * Set all tiles to lit/visible
LI R1,VMAP
LI R2,LMAP
MEVW1 MOVB *R1+,*R2+
DEC R0
JNE MEVW1
RT

* Calculate mob position in viewing window
* Returns window index (0-169) in R3, -1 = not in window
MOBWIN DATA VWS,MOBWN0
MOBWN0 MOVB @SLANT,R2 * Get orientation into R2
SRL R2,8
MOV @DIRY(R2),R0 * Set R7 to 1 (left), 0 (none), or -1 (right)
NEG R0 * Negate R0
MOV @W7,@WMOB * Store 7 in WMOB
MOV @WN7,@WMOB+2 * Store -7 in WMOB+2
MOV @MAPMOB+2,R2
MOV R2,R4
SRL R2,8 * Set R2 to mob Y
ANDI R4,>00FF * Set R4 to mob X
S @Y,R2 * Subtract player y from mob y
CI R2,7 * Check right vector
JLT MOBWN1
MOVB @EDGES,@EDGES
JEQ MOBWN5
S @VWIDTH,R2 * Subtract wrap offset
MOBWN1 CI R2,-7 * Check left vector
JGT MOBWN2
MOVB @EDGES,@EDGES
JEQ MOBWN5
A @VWIDTH,R2 * Add wrap offset
CI R2,6 * Check left vector again
JGT MOBWN5
MOBWN2 MPY R2,R0 * Multiply Y offset by R0
S R1,@WMOB * Adjust positive boudnary for X
S R1,@WMOB+2 * Adjust negative boundary for X
MOV @WMOB,@WMOB+4
DEC @WMOB+4
S @X,R4 * Subtract player x from mob x
C R4,@WMOB * Check down vector
JLT MOBWN3
MOVB @EDGES,@EDGES
JEQ MOBWN5
S @HWIDTH,R4 * Subtract wrap offset
MOBWN3 C R4,@WMOB+2 * Check vector
JGT MOBWN4
MOVB @EDGES,@EDGES
JEQ MOBWN5
A @HWIDTH,R4 * Add wrap offset
C R4,@WMOB+4
JGT MOBWN5
MOBWN4 MPY @W13,R2 * Multiply by 13 (window width)
AI R3,84 * Add center offset
A R4,R3 * Add X delta
A R1,R3 * Add shift delta
MOV R3,R3
JLT MOBWN5
CI R3,168
JGT MOBWN5
MOV R3,@>0006(R13) * Copy back to calling routine
RTWP
MOBWN5 SETO @>0006(R13)
RTWP

* Check tile at index in R3
CHKTLE MOVB @LMAP(R3),R4 * Check light level
JEQ CHKTL2 * If not lit, is automatically blocking
CB @CELEV,@EMAP(R3) * Check current elevation against target tile
JEQ CHKTL3 * If equal, continue to opacity test
JLT CHKTL2 * If less, go to block
CHKTL1 CLR R5 * Clear R5 (open)
JMP CHKTL4
CHKTL2 SETO R5 * Set R5 (closed)
JMP CHKTL4
CHKTL3 MOVB @VMAP(R3),R4 * Copy tile to R4 low byte
SRL R4,8
ANDI R4,>007F
MOVB @TILES(R4),R5 * Copy tile code into R5 high byte
CHKTL4 RT

* Check for map edges
* R1 = Y, R2 = X
* R0 returns 0 if no violation, 1 if vertical, 2 if horizontal, 3 if both
EDGCHK CLR R0 * Set to 0
C R2,@HWIDTH * Check X against horizontal width
JL EDGCH1
INCT R0
EDGCH1 C R1,@VWIDTH
JL EDGCH2
INC R0
EDGCH2 RT

* Determine index on map
POSCLC MOV R1,R2
MPY @W13,R2
A R0,R3
RT

Conclusion
Despite the numerous calculations going on, the map view creation is pretty fast, enough that I had to introduce some artificial delays. I did notice that the more mobs on the map the more impact it had on performance. For that reason, there is a limit of mobs per map, and I actually broke up some maps into more than one map to remove performance problems.

So that’s it with maps and part 2! I’m not sure what part 3 will cover yet, I’m open to feedback.
 

mindx2

Codex Roaming East Coast Reporter
Patron
Joined
Feb 22, 2006
Messages
4,431
Location
Perusing his PC Museum shelves.
Codex 2012 PC RPG Website of the Year, 2015 Codex 2016 - The Age of Grimoire RPG Wokedex Serpent in the Staglands Divinity: Original Sin Project: Eternity Torment: Tides of Numenera Wasteland 2 Shadorwun: Hong Kong Divinity: Original Sin 2 BattleTech Pathfinder: Wrath I'm very into cock and ball torture I helped put crap in Monomyth

Fatberg Slim

Educated
Patron
Joined
Mar 4, 2022
Messages
75
Location
Q-Link
I just wanted to put in a plug for this very fine game. It received a deservedly high rating in the Codex’s 2021 GOTY rankings, and had I been able to vote it definitely would have received my highest score for the year (although fwiw the only other game on the list I played was Fallout Olympus 2207). While I have some admittedly old-school RPG sensibilities, I had a great time with RoA and found it very absorbing from beginning to end. I’m not familiar with the technical capabilities of the TI-99 (I was a C64 man :obviously:) but just based on its age as a contemporary of the VIC-20 I was pleasantly surprised by the amount of content and detail in the game.

My party for those who care:
- Hero (obvious MVP, can and did do everything)
- Thief (convenient auto-detect ability, effective damage dealer via dual-wielding and hit-and-run tactics although her effectiveness dropped off a lot towards the end of the game)
- Crusader (almost as good as the Hero in combat, consistently powerful damage and healing magic)
- Paladin (indestructible and good buffing magic but low damage output due to limited weapon selection)

High points:
- Game does not waste your time. Combat is brisk yet tactical, and world-map travel is reasonably fast
- Extremely fun exploration and varied areas with their own character
- Unique gear with various special bonuses (although not all was smooth – see below)
- Cool elevation mechanic on the map that affects visibility, which while not game-changing added some flavor and had its uses

Neutral point:
- Lots of random combat, although this wasn’t a problem until the endgame when I no longer needed loot and knew leveling wouldn’t help me

Low points (relative):
- Cumbersome inventory system, consumable items not especially useful, and some skills being much more useful than others, although it could be argued these traits make RoA a true heir to the 80s RPG experience
- Endgame:
That completely dark maze, holy shit :argh:

Some design/plot points I wasn’t sure about:
- For the weapons that had on-hit effects, do they either cause damage or the effect but not both? It seemed like this was the case, and for this reason I generally stuck with damage-only weapons, which was a particular problem for the paladin since most of the top-tier blunt weapons had on-hit effects. I appreciated the itemization but it was painful to hit a monster 3X in a row with the same confuse/whatever effect without causing any damage. Yelling “FINISH HIM!” at the screen every time this happened never got old though
- Towers:
Is it necessary to go in all the towers in order to win? The secret words used to enter the towers are used for something else, but I don’t recall ever learning or doing anything in the towers that seemed necessary. I finished the game a while back so I could be forgetting something
- SE part of map:
Who sent those assassins? I went to their base and tried to fight the leader, but it seemed like nothing came of this. I assumed the Overlord sent them but after finishing the game that seems unlikely

Anyway, great game 5/5 would relive the past with again.
 

adamantyr

Arcane
Developer
Joined
Apr 5, 2019
Messages
154
Location
Marysville, WA
Some design/plot points I wasn’t sure about:
- For the weapons that had on-hit effects, do they either cause damage or the effect but not both? It seemed like this was the case, and for this reason I generally stuck with damage-only weapons, which was a particular problem for the paladin since most of the top-tier blunt weapons had on-hit effects. I appreciated the itemization but it was painful to hit a monster 3X in a row with the same confuse/whatever effect without causing any damage. Yelling “FINISH HIM!” at the screen every time this happened never got old though
- Towers:
Is it necessary to go in all the towers in order to win? The secret words used to enter the towers are used for something else, but I don’t recall ever learning or doing anything in the towers that seemed necessary. I finished the game a while back so I could be forgetting something
- SE part of map:
Who sent those assassins? I went to their base and tried to fight the leader, but it seemed like nothing came of this. I assumed the Overlord sent them but after finishing the game that seems unlikely

Thank you, I'm glad you enjoyed the game!

Regarding weapons with special effect properties, ideally both damage and the effect should be applied. Unfortunately my current engine design treats them as separate effects and only one can be processed. I'm testing a fix in the current test build to see if I can have both apply, but it may only show the secondary effect on screen while still dealing damage.

- Towers:
You don't have to visit all the towers, they are a source of information, spellbooks, and skill training primarily.
- SE part of map:
The Overlord did, it was to keep up appearances and bring you to him.
[/QUOTE]
 

Fatberg Slim

Educated
Patron
Joined
Mar 4, 2022
Messages
75
Location
Q-Link
Thanks! Yes I thought the weapon effects might have been implemented that way for technical reasons rather than by design. They were cool though.

Also, one other thing I liked but forgot to mention was the interactions involving the recruitable NPCs. They were rare but notable and unexpected for a game like this.
 

As an Amazon Associate, rpgcodex.net earns from qualifying purchases.
Back
Top Bottom