Conquest! - Free Real-Time Strategy Game
  • Home
  • Start Playing
  • How to Play
  • About
    • Development Blog
    • History
    • Press Kit
    • Server Change Log
    • World Events
  • Login Help
  • Contact Us
  • Privacy Policy

2025 Development Summary

12/20/2025

0 Comments

 
​Greetings conquerors, it's time for the 2025 recap. This was the year AI met Conquest!. AI has been an incredible accelerant, enabling many enhancements that would have been impossible otherwise.

Here are the highlights of changes:

 ✨ Enhancements
• Adjoining region travel
  • Travel by land to an adjoining region (no boat required!)
  • Adjoining cities now show the "stables" icon on the map
•Flexible troop choice for all classes:
  • 2 options per class
  • Bards: 3 options
  • Vampires: remain limited to their own type
• Increased maximum structures to 400/region
• Honorary legendary NPCs created from historic player names (e.g., RavenfireTheLegend)
• Changing class or troops now costs 50% of troops (siege is unaffected)
• Increased chance for bonus goods
• New espionage missions: Scuttle and Scramble
• New quest riddle (AI generated)
• New emperor challenges
• New log format
• Reduced movement point cost for many skills

⚔️ Combat
• New aerial strikes
• Capturing structures now awards a minimum of 1% spoils
• Overhauled ship designs and naval battles
• Pirate fleets vary based on level
• New troop attributes: mode bonus, rugged, splash
• Range now varies by short, medium, or long distance

🧙‍♂️ Classes
• Assassinate can target a player with a hero or the hero directly
• Bandit hideouts hold 500 troops; Ranger forts 400
• Most Ranger buffs can be applied to self or others
• Brewed potions now persist after class changes

🖥️ UI
• New panel and popup window transitions
• Added outlines to some icons to improve visibility
• New command console
• Kingdom and Army buffs visible from HUD buttons
• Players are automatically added to target lists less frequently
• New troop and ship sketches (AI generated)
• View all troop and ship attributes
• Overhauled city management
• Improved tokenization to prevent mismatch errors
• New class and city backgrounds (AI generated)
• New panel backgrounds
• Significant technical improvements

☠️ NPCs
• NPCs will not buy the last of any goods in markets (except troops)
• Updated NPC responses based on persona (Attacker or Defender)
• Increased variability in NPC actions

 ⚙️ Game Server
• All city and tavern names randomized at the start of each Age
• Standardized battle mechanics
• Backup configuration data each world season
• Removed redundant commands
• Switched token protocol
• All lists now shuffled using Fisher-Yates algorithm
• New notification system (email, mobile, Discord, AI assisted)
• New threading system (AI assisted)

🌀 Misc.
• You no longer need an established kingdom to use adjoining region travel or portal
• Summon Dragons can now be used across regions
• Severe weather while sailing increases the odds of encountering Atlantis
• Sceptre of Death kills troops versus players
• Updated weather effects
• Expeditions no longer prevent attacks in region
• Traveling to anti-magic city no longer destroys items or potions
• Ships may restock during age of isolation
• Ages now last a minimum of 21 days, maximum 35 days
• Updated name and win message generators
• Bounties now based on attacks rather than eliminations
• Many message cleanups
0 Comments

AI and Me

8/9/2025

0 Comments

 
This past month, I decided it was time to refresh the panels as it had been over 4 years since they were last updated. I first reached out to the graphic artist I’ve worked with for years, but while he’s brilliant with icons, large panels aren’t his specialty. That’s when I decided to give AI a try.

After consulting with ChatGPT, I decided to use InvokeAI, because its free, runs locally, and doesn't require a cloud service. It was very easy to setup but it took several hours of experimenting with models and prompts before I could start producing useable results.. When I finally settled on models and was comfortable-ish with the prompts, I started work on the new panels

ChatGPT would generate the initial prompt and then we would refine it over multiple iterations until I got something close to what I wanted. For the prompts, not only do the words you use matter but also the specific order, phrasing, and negative prompts! Simply changing a single word, "warrior" to "fighter" for example, could completely alter the generated image. A few of the generated images were nightmare fuel (mouths in weird places or extra limbs). And the models really struggle with specifics. For example, if I told it I wanted "one statue" I might end up with one. Or five, depending on the other words in the prompt, the order of the words, etc.

I used the "generate" functionality in Invoke, which uses a text prompt, rather than reference images. In my experiments the models just didn't adhere closely to the reference image provided. There are many settings within Invoke. My final settings were: Euler as the scheduler, 100 steps, and a CFG Scale of 10. I also fixed the seed so all my images would  have a consistent style. I used the Architecture and Juggernaut models exclusively.

Sometimes I had to compromise between my original vision ("a sphinx in a hidden temple") and what the AI could reliably produce ("a statue of a winged woman holding a candle"). Being willing to compromise was a major hurdle for me initially. It was rare that I got what I wanted on the first try. But sometimes the AI would surprise me with something I hadn't considered and it turned out to be perfect.

Here are a few before and after pictures. First up is "ancient ruins", used for searching for an artifact.
Picture
Picture
Here is the one used for land travel.
Picture
Picture
The best advice I can give: patience. It took hours to create and refine the prompts, slowly iterating until the image was something I was happy with. The process can be frustrating, but if you stick with it, the results are worth it. 

​Until next time!
0 Comments

Threads and Me

7/17/2025

0 Comments

 
Since at least 2015, the Conquest! server has utilized threads. But the implementation was rudimentary: the server would process all incoming commands, build queues of messages to send out, and then create threads to deliver them to clients. The main thread would then wait until all threads completed  before repeating the cycle.

In June, I decided to take a closer look at overhauling the thread management system. Part of this was driven by the need to create a separate thread to send alerts. Sending emails with curl could take several seconds — retrieving an OATH token from Gmail was time consuming and blocked the main thread.

So I set out to create 9 persistent threads: 1 dedicated for sending alerts and 8 to update clients. I used ChatGPT to help with the framework and debugging. There are a few points of the implementation that I found particularly interesting.

1. Prevent Socket Blocking
To prevent one socket connection from blocking others, a timeout had to be implemented in the thread's processing loop. The pseudocode looked roughly like this:

foreach socket:
pthread_mutex_lock;
while(!stuff) {
pthread_cond_timeout;
}
pthread_mutex_unlock;
process_stuff;

I hadn't used the pthread_cond_* functions before, so this was all new to me. If the worker thread was signaled by the main thread, it would wake up and start processing. If the timeout expired, the thread could move to the next socket.

2. Shared Data and Mutexes
It was critical to ensure all values accessed between threads were protected with mutexes. This seems obvious, but in a mature codebase such as Conquest! (the server was ported to C in 1997), there were references to these shared values sprinkled across the code base and tracking them down took some time (and caused some weirdness along the way).

3. Handling Partial Sends
As part of this upgrade I implemented additional functionality to handle partial sends to clients. In the past, if send() returned any error the thread would simply drop the connection. Now, if the error code is 
EAGAIN or EWOULDBLOCK, the thread remembers where it left off and retries on the next cycle. This is to ensure all data is sent to the client (at least from the server's perspective).

4. Select/Poll Timeout
A bit of trivia I learned concerned select/poll. I switched from select to poll and along the way learned that both of these functions check if file descriptors are ready for IO at the time they are called. If not, they sleep for the full timeout. I had mistakenly assumed that incoming IO  would  interrupt the wait. The poll man page states that it "blocks until a file descriptor becomes ready" but that wasn't the behavior I observed (and contradicted  ChatGPT's explanation too). I reduced the timeout from 1000m s to 50ms and the client became noticeably more responsive.

Until next time!
0 Comments

At last, notifications

5/24/2025

0 Comments

 
Picture
Since 2015, Conquest! has sent alerts using email. This has worked fine for years but when I first created a UI client for mobile devices, I've wanted to add push notifications. When I looked at this a few years ago, notification services cost money and the integration seemed complex.

But recently I was chatting with ChatGPT and asked about adding notifications to Conquest!. It told me about Firebase and its available free messaging service and after sharing some code samples, we were off to the races.

Part 1: Firebase Project
Creating a new account and project inside Firebase was easy. I created a service account, ensuring that I downloaded the  service account JSON file when prompted as you can't retrieve it again later. The service account information is used to request the access token to send notifications (more on that below).

Next I added my iOS and Android applications to my Firebase project and downloaded two additional configuration files, which are required for integration with Unity.

Part 2: Server
The Conquest! server is the brains of the entire game. In order to send notifications to clients, the server must get an OAUTH2 access token, which expires every hour,  from Firebase. The game server is written in C which is great for performance but as ChatGPT said "It’s doable but not fun. C wasn’t made for working with JSON + Base64 + JWT + RS256 + HTTPS, so expect manual plumbing." After reviewing the pseudocode ChatGPT provided, I decided to go with the alternative suggestion, creating a Python script that the C server would call to receive the access token.

import google.auth
from google.oauth2 import service_account
import google.auth.transport.requests
import sys
# The OAuth 2.0 scope required to send FCM messages
SCOPES = ["https://www.googleapis.com/auth/firebase.messaging"]
def get_access_token(service_account_file):
    credentials = service_account.Credentials.from_service_account_file(
        service_account_file,
        scopes=SCOPES
    )
    auth_req = google.auth.transport.requests.Request()
    credentials.refresh(auth_req)
    return credentials.token
if len(sys.argv) < 2:
    print("Usage: fcm_token.py <path_to_service_account_json>")
    sys.exit(1)
service_account_file = sys.argv[1]
token = get_access_token(service_account_file)
print(token)

This required installing the "google-auth" Python module on both my development and production servers.

Now I had the token but I still had to construct the JSON messages. Fortunately, I switched from XML to JSON last year so the server was already using the JSON-C library. I also recently switched from using mailx to send emails to using curl. Both of these changes helped with the development of the new notification system.

The server keeps the access token in memory and checks its age to see if its greater than 55 minutes old. If so, it requests a new one by opening a pipe and invoking the Python script above. Next it constructs the notification request using JSON-C and then sends it to Firebase using curl. 

When a client connects to the server and provides its token, this is stored in a device structure.

Part 3: Unity3d
The first item to do was install the Firebase SDK into Unity. Since the notifications are coming from the server and the client isn't receiving them directly, I only needed to import FirebaseMessaging.unitypackage. I placed the the application configuration files downloaded from Firebase in Assets/StreamingAssets (Android) and the root Assets folder (iOS).

Next I had to add some code in my application to initialize Firebase and receive the client token. I needed to send this token to the server so it knew where to send the notifications.

When I first ran the code in the Unity editor I received this error: Database URL not set in the Firebase config.
After some back and forth with ChatGPT, it was determined that the URL had to be included in the Android and iOS configuration files i.e., "databaseURL": "https://your-project-id.firebaseio.com"

After correcting that error, I was able to run the code in the Editor but not actually test the notifications.

Part 4: iOS
Naturally, building for iOS was the most complex. From the Apple developer portal, I had to create a new p8 Key used for push notifications and add that to my Firebase project.  My development box runs Windows, so after Unity was done I transferred to a Mac.

Unity doesn't include all the framework files required to build in XCode (why?!?). In order to add those, I needed to install "CocoaPods". Installing this the first time didn't work (of course) because most Macs ship with an old version of Ruby.

In order to update it, I had to use Homebrew, which also wasn't installed. So first I had to install Homebrew and then update Ruby (and adjust my PATH environment variable to use the new versions and tools). Finally, I could install CocoaPods. All of this was done so I could execute "pod install" from the Mac inside the Unity project folder to add all the framework files Firebase required.

I opened the xcworkspace file that pod created in XCode. Certain key capabilities cannot be set inside Unity, so I had to add "Background Tasks" (Remote notifications) and "Push Notifications" to the project. Once all of that was done, I was finally able to build the iOS client. Whew!

Part 5: Android
By contrast, building for Android was much easier. In the Build Settings I had to ensure Custom Main Gradle Template and Custom Launcher Gradle Template were selected. I had to edit the mainTemplate.gradle and replace "**PACKAGING**" with "**PACKAGING_OPTIONS**" and add "**BUILT_IN_EXTRAS**" and "**EXTERNAL_SOURCES**" at the bottom. The Android build completed on Windows but when I first tested no notification showed up.

This is because by default Android suppresses notifications if the app is open. Once my tester closed the Conquest! application, the notification worked! There is a workaround to show notifications when the application is open but I decided to leave it as is for now.

Conclusion
Working with ChatGPT during this process was invaluable. I could have done this with Google searches but it would have taken far more time as I tried to piecemeal solutions from across the Internet. ChatGPT isn't perfect, however, and there were a few times where it led me down an incorrect path. But overall, it is a great tool and I finally have push notifications working for Conquest!. Huzzah!
0 Comments

Removing the superfluous

4/18/2025

0 Comments

 
When Conquest! was solely a text based game, it made sense to send players all the proper names for all the objects in the game (e.g., troop names, ship configurations, magical items, etc.). For example, when sending kingdom details, the server would send something like this:

 { "Id": "4174", "Body": [ "168 Move", "34 Grove", "4357845 Gold", "180" ] }

The "Move", "Gold", and "Grove" text descriptions are fine for a text client but they were being discarded by the GUI! This meant lots of unnecessary data being sent to the client. I decided to finally address this situation, similar to the effort last year for troops and reports (you can read the blurb here).

Some of the information, for example the names of magical items, still needs to be visible in the client. If it isn't sent every update, how should the client get this information? I thought of three options:
  1. Hard code the values in the client. It's true these values rarely change  but I didn't like how this option made me feel. :)
  2. Push the values once on client startup. This was the option I was leaning towards but it still meant lots of unnecessary traffic.
  3. Store the values in JSON files and update when necessary.

I ended up going with option three and adding three additional JSON files to the client: magical components, magical items, and ships. These files are retrieved  when they don't exist or when the server instructs the client to update them. In this way I get the best outcome: data is sent once and only updated when required.

This conversion ended up breaking all previous clients, since I didn't build in a way for the server to send the data the old clients needed. The game was still playable, but items and ships couldn't be viewed or utilized (not a great experience). Live and learn I suppose.

Until next time!
0 Comments

2024 Summary

12/4/2024

0 Comments

 
Greetings conquerors, it's time for the 2024 recap. This year focused mainly on "tech debt", with the two largest efforts being the communication protocol switch and text message overhauls.

Switching from XML to JSON reduced the payload sizes by 10-30%. This helps with client performance on slower connections (see separate blog post for further details). Additionally, I recently removed the troop name (e.g., soldier, archer, knight, etc.) and mode (i.e., infantry, cavalry, flight) from the battle and spy reports. Sending this text was wholly unnecessary and further reduced the payload size for those reports. Imagine 8 armies (4 per side) with 8 troops each plus the casualties for each side; that is 80 instances per battle report where the troop name and mode were being sent. Yikes!

Conquest! has ~3000 pieces of text, which include button labels, tips, help, email notifications, and success/error messages. This text was developed over years and varied wildly in tone and grammar. Also, there were many duplicate messages. This year I focused on standardization and the removal of duplicates by creating server-side helper functions for common messages (e.g., cannot use an item or skill, capturing or destroying goods, etc.). These functions are called across the game as needed and provide a more consistent experience for players.

Here is the complete list of changes.

 Server
  • Switched communication protocol from XML to JSON
  • Removed unnecessary data from battle/spy reports
  • Cleaned up dozens of messages, removed ~60 duplicate/unused ones
  • Created functions for common messages
  • Improved pushing updates to client

 Classes
  • Created troops for Alchemist class
  • Song of Celestial adds immortal reinforcements when attacking and defending
  • Added "rugged" troop attribute

 NPCs
  • Added Attacker/Defender personas for active NPCs
  • Active NPCs will not randomly attack any PC unprovoked
  • Overhauled active NPC logic

 UI
  • Members in different alliances can be selected for spy/attack
  • Clicking anywhere outside popup window will close it
  • Added setting password to new player wizard
  • Added support to email forgotten passwords
  • Added one-time promotional code when setting an email address
  • Fixed bug causing app to "forget" player

Misc.
  • Reports for summoned battles copied to player using skill
  • Rename "continent" to "region"
0 Comments

JSON Conversion

6/13/2024

0 Comments

 
Since Conquest! was converted to the C language circa 1998, it has used an XML format to exchange information. This worked fine for years but I recently became concerned with the payload sizes, as the amount of data the server needed to send the client has increased. Additionally, I was not using any advanced XML capabilities (e.g., meta data support) that would warrant its continued use. I started looking at JSON and became convinced after seeing payload sizes reduced by 10-30%. For example, here are examples of common commands and sizes (in bytes).

Command

XML

JSON

Difference

Person

1043

916

-13%

Market

1471

1134

-23%

Map

562

421

-25%

Weather

1352

959

-30%

Review

4491

2988

-33%

None of the player or world data is stored client-side, so each action a player takes requires updates from the server. Over time, these will add up to huge savings and should improve the end user experience. Additionally, I wanted to ensure backwards compatibility with existing XML based clients, so the server had to support both formats until I force a client update. I'm using the client version number to determine which to try first.
Part of the challenge was finding existing libraries for both C (server) and Unity3D (client) to handle JSON parsing.
For the server, I decided on json-c. The library itself is great but I found the documentation on the GitHub repository lacking. It wasn't until I found this tutorial that it all finally clicked. This tutorial covers everything from installation to memory management. Another good reference was the header file documentation.
Now that the server was covered, I turned to the client. I installed the Newtonsoft Json package (available for free) and followed the guide here. Serializing and Deserializing the response back from the server took some time to get working. I ended up having to break down the JSON object by object and add them back in one at a time to locate my errors. I found this site very helpful when creating my initial classes.
As part of this conversion, I also moved from using Unity's PlayerPrefs to a JSON configuration file. Over time, the amount of data I was storing in PlayerPrefs had gotten large and I needed to represent more complex structures. I was able to use Newtonsoft for both the responses from the server and management of the configuration file. I even wrote a conversion function to silently upgrade client from PlayerPrefs to the new settings file.
It was a lot of work over several days, but I'm pleased with the results.
UPDATE: After some consideration, I ended up switching from Newtonsoft to the standard JSON utility included in Unity. I didn't need the advanced capabilities of Newtonsoft and the default utility works fine. One item which did cause me problems is that all classes and subclasses need to be identified with [System.Serializable] for the default utility to work properly.
0 Comments

2023 Summary

12/30/2023

1 Comment

 
Here is the summary of enhancements made to Conquest! in 2023.

One important item left off is finally utilizing Application.persistentDataPath to store the ~3,000 messages utilized throughout the game. In the past, if I wanted to update messages I would have to re-deploy the entire client. Now the client is pulling the messages down when the server tells it to. This has been a tremendous benefit to me and the players as I can quickly fix spelling or grammatical errors (or even add new error messages).

There are Unity packages which allow for dynamic translations but when I implemented the UI circa 2016, I didn't know about them. And when I did learn of them, I was concerned about performance. In hindsight, I may have taken a closer look at those packages vs doing all the translations myself.
 
Major Enhancements
  • Launch expeditions to gain goods and discover secrets
  • Hire NPC mercenaries to aid your alliance
  • NPCs have attacker or defender personas
  • Player defined seasons

Alliances
  • Alliance levels grant buffs to all members
  • View all members in other alliances
  • Share kingdom report with alliance
  • Alliance logs record major member events

 Classes
  • New classes: Alchemist, Druid, Bard
  • New class skills: Hijack (Bandit), Ambush (Barbarian), Persistence, Surge (Fighter), Restoration (Ranger), Clone (Vampire)
  • Barbarians can loot move, items from cities
  • Mages are no longer honor bound
  • Vampire level drain changed to life (move) drain
  • Vampire masters start with minions based on level

Events
  • Random and weather events may occur hourly
  • Seasonal events (Winter=Joy, Spring=Love, Summer=Heat, Autumn=Horror)

UI
  • New fonts for improved readability
  • Support for new languages and character sets
  • Support for Emojis
  • Email and NPC private messages are language specific

Minor Enhancements

Artifacts
  • Dagger protects against bandit skills
  • Tree protects against blight skills
  • Rod allows players to attack 2 levels down

Heroes
  • Alchemist, Barbarian Leader, Diplomat, Ninja, Queen of Spiders allow class skills half-level
  • Ninja protects against bandit skills
  • Queen protects against blight skills

Misc.
  • All classes can buy/sell/quest components
  • Per player cooldown for pirate attacks
  • Support to delete accounts
  • Promote to level 10 after reaching level 9 on all continents
  • Level 10 players receive buffs based on class
  • Magic shops randomly placed
  • Goods received scale with level
  • Changing class or troop type preserves siege
  • Salvage goods from naval battles
  • Honor bound players start with fair honor
1 Comment

Push vs Pull

8/14/2023

0 Comments

 
When Conquest! was first developed in 1993, it used a very simple model to exchange data between the server and players. A player would send a command and the server would reply with an appropriate response. If a player needed additional information, they would issue another command to retrieve it. This worked well for a text-based UI.

When the UI was developed in 2016, essentially the same model was used. However, it quickly became apparent that additional information was needed to update the UI. Therefore, after a player issued a command and the server sent the initial response, the client would send additional commands to the server to retrieve the rest of the information. For example, consider this sequence:
  1. Player sends Buy
  2. Server sends Response
  3. Client Sends Person
  4. Server sends Person
  5. Client Sends Review
  6. Server sends Review
  7. Client Sends Market
  8. Server Sends Market
This solved the issue of missing ancillary information but was inefficient and meant that any updates required a client deployment.

In January of 2021, I switch from a pull to a push model. With this model the server would decide what information to send after processing a command. The above example now looks like this:
  1. Player sends Buy
  2. Server sends Response
  3. Server sends Person
  4. Server sends Review
  5. Server Sends Market
This reduced the amount of network traffic and server-side processing and meant that any fixes could be done quickly without a client update. This has worked well for 2.5 years, but I have recently become concerned that data was being sent multiple times as the web of what to send and when became difficult to trace (each individual command was responsible for deciding what data to send back to the client).

I have updated the pull model to use a centralized function to examine the player structures after a command is executed to dynamically determine the data required to send to the client. In this way, I do not have to worry about a command missing something (or sending too much data). The correct data is sent automatically each time.
0 Comments

Localization and Floating Points

9/24/2022

0 Comments

 
After a recent deployment, a player named TaxE contacted me with an unusual bug: none of the cities were showing on the travel maps! This renders Conquest! virtually unplayable. During the course of troubleshooting with TaxE, we discovered that this problem affected not just his Android device but his laptop too. Switching from Wifi to Cellular did not have any effect. I was stumped until I began brainstorming with long time player, Elric.

Until recently, Conquest! used integers to represent cities on a map in a 10x10 grid. These coordinates are sent from the server to clients as strings, where they are transformed to numbers and used to render the cities. During a refresh of the maps, I realized I needed greater precision for positioning so I switched from integers to floating points. Naturally, I changed my code: int.TryParse(Input, out Number) became float.TryParse(Input, out Number)

When talking with Elric, he asked where TaxE was located. Turns out, TaxE was outside the US where a comma is typically used as a decimal separator vs a period in the US. Using the original float.TryParse(), clients outside the US were transforming city coordinates such as 3.7 to 37, rendering the cities far out of bounds of my 10x10 grid!

A quick Google search revealed the solution: float.TryParse(Input, System.Globalization.NumberStyles.Float, System.Globalization.CultureInfo.InvariantCulture, out Number);

By forcing TryParse() to utilize InvariantCulture, which by default uses a period as a decimal separator, the numbers are now being transformed correctly.

A huge thanks to both Elric and TaxE for helping work through this one!

0 Comments
<<Previous

    Author

    James has been working on Conquest! since 1993.

    Archives

    August 2025
    July 2025
    May 2025
    April 2025
    December 2024
    June 2024
    December 2023
    August 2023
    September 2022
    July 2022
    October 2021
    September 2021
    April 2021
    September 2020
    February 2017
    December 2016
    October 2016
    September 2016
    August 2016
    July 2016
    May 2016
    April 2016
    March 2016
    February 2016
    January 2016
    December 2015
    November 2015
    October 2015
    September 2015
    August 2015
    July 2015
    June 2015
    May 2015

    Categories

    All

    RSS Feed

Copyright 2025 GreenLion Gaming All Rights Reserved
  • Home
  • Start Playing
  • How to Play
  • About
    • Development Blog
    • History
    • Press Kit
    • Server Change Log
    • World Events
  • Login Help
  • Contact Us
  • Privacy Policy