Skip to content

R28 tile improvements#34

Open
instafluff0 wants to merge 16 commits into
maxpetul:masterfrom
instafluff0:r28-tile-improvements
Open

R28 tile improvements#34
instafluff0 wants to merge 16 commits into
maxpetul:masterfrom
instafluff0:r28-tile-improvements

Conversation

@instafluff0
Copy link
Copy Markdown
Contributor

@instafluff0 instafluff0 commented May 8, 2026

This is my PR one of three for R28. Those are:

  1. r28-tile-improvements (this)
  2. r28-seasonal-cycle
  3. r28-animations

I'd suggest merging them in that order, as (3) was branched from (2) and depends on the seasonal-cycle code.

This PR

  1. Allows for districts as genuine custom tile improvements used by only one city at a time, using the config:
type                   = tile-improvement
ai_build_strategy      = tile-improvement
tile-improvements
  1. Allows workers to be consumed and despawned after completing a district, like colonies.

    consumes_worker = 1
    
  2. Allows the default resource on a tile to be suppressed if a district is built over it

    subsumes_tile_resource = 1
    

Comment thread injected_code.c Outdated
Comment thread injected_code.c Outdated
}
if (worker_to_consume != NULL) {
if ((p_main_screen_form != NULL) &&
patch_Leader_is_tile_visible (&leaders[p_main_screen_form->Player_CivID], __, worker_to_consume->Body.X, worker_to_consume->Body.Y)) {
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Surely the worker should get despawned even when no one's looking?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, sorry this was a dumb half-baked leftover from my experiments to show worker animations when they despawn, rather than simply disappearing. Because the camera was always on the tile when I finalized the code I hadn't realized I'd even left it.

Thanks for catching that - removed now.

Comment thread injected_code.c Outdated
} else if (slice_matches_str (key, "impassable") || slice_matches_str (key, "impassible")) {
struct string_slice val_slice = *value;
int ival;
} else
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Notice how the lines around this insertion are considered to have been changed even though their contents is the same? That indicates the line endings have been changed. Please make sure that all lines in injected_code.c have dos-style endings. This seems to be a recurring issue with Codex and maybe other AI code editors that the edits they make have unix-style endings regardless of what's standard in the file they're editing. I had this problem before so I had Codex write fix_line_endings.py, which ensures that all lines in all files of the project have the expected ending style. That may be useful for you.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I know what you mean, but I'm not sure this is a case of that. I just ran it and I don't see anything with line endings off in injected_code.c. Am I misunderstanding the script?

Edit: scratch that, I think I had my local branch in a funny state due to a misbegotten git reset, trying again...

Edit 2: Ok, should be good now. I'll run this for the other branches as well.

Comment thread injected_code.c Outdated
}
}

if (is->current_config.enable_districts) {
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is the purpose of this block of code? For food and shields it's redundant because the base City::calc_tile_yield_at returns the values from Map::calc_food_yield_at and Map::calc_shield_yield_at, and those Map functions are already patched to handle improvement districts and generated resources. This block is necessary to calculate commerce yield correctly, but why place that here instead of in a patch for Map::calc_commerce_yield_at?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right. Short answer: I got a crash every time I tried to patch Map::calc_commerce_yield_at.

If I remove this block and add:

inlead, 0x5d7AD0, 0x0, 0x0, "Map_calc_commerce_yield_at", "int (__fastcall *) (Map * this, int edx, int tile_x, int tile_y)"

int __fastcall
patch_Map_calc_commerce_yield_at (Map * this, int edx, int tile_x, int tile_y)
{
	if (! is->current_config.enable_districts)
		return Map_calc_commerce_yield_at (this, __, tile_x, tile_y);

	Tile * tile = tile_at (tile_x, tile_y);
	if ((tile != NULL) && (tile != p_null_tile)) {
		struct district_instance * inst = get_district_instance (tile);
		if (inst != NULL &&
		    district_is_complete (tile, inst->district_id)) {
			if (! district_uses_tile_improvement_rules (inst->district_id))
				return 0;
			City * yield_city = get_city_ptr (tile->Body.CityAreaID);
			if (! district_tile_bonus_applies_to_city (tile, inst->district_id, yield_city))
				return 0;
			struct district_config * cfg = &is->district_configs[inst->district_id];
			int commerce_bonus = 0;
			get_effective_district_yields (inst, cfg, NULL, NULL, &commerce_bonus, NULL, NULL, NULL);
			if ((cfg->generated_resource_id >= 0) &&
			    (cfg->generated_resource_flags & MF_YIELDS) &&
			    district_generates_resource_for_civ (tile, inst, cfg, yield_city->Body.CivID)) {
				Resource_Type * res = &p_bic_data->ResourceTypes[cfg->generated_resource_id];
				commerce_bonus += res->Commerce;
			}
			return commerce_bonus;
		}
	}

	return Map_calc_commerce_yield_at (this, __, tile_x, tile_y);
}

I get a crash upon founding a city or trying to open the city screen. I could not figure out why and probably should have tried to determine if the arguments we had for that function in Ghidra were correct, but did not, so this seemed a reasonable workaround. But I hear you and agree it is duplicative. I think my development of this branch also pre-dated our earlier discussion of how to figure this stuff out using Ghidra.

Let me take another stab at it.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Update: I believe I've got things running now and corrected. Ghidra had RET 0x18 for Map::calc_commerce_yield_at, so 6 args, which I'm fairly certain were exactly the same as Map::calc_food_yield_at.

I updated civ_prog_objects accordingly, removed the block in patch_City_calc_tile_yield_while_gathering, and added patch_Map_calc_commerce_yield_at.

Copy link
Copy Markdown
Contributor Author

@instafluff0 instafluff0 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please see comments below.

Comment thread injected_code.c Outdated
} else if (slice_matches_str (key, "impassable") || slice_matches_str (key, "impassible")) {
struct string_slice val_slice = *value;
int ival;
} else
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I know what you mean, but I'm not sure this is a case of that. I just ran it and I don't see anything with line endings off in injected_code.c. Am I misunderstanding the script?

Edit: scratch that, I think I had my local branch in a funny state due to a misbegotten git reset, trying again...

Edit 2: Ok, should be good now. I'll run this for the other branches as well.

Comment thread injected_code.c Outdated
}
if (worker_to_consume != NULL) {
if ((p_main_screen_form != NULL) &&
patch_Leader_is_tile_visible (&leaders[p_main_screen_form->Player_CivID], __, worker_to_consume->Body.X, worker_to_consume->Body.Y)) {
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, sorry this was a dumb half-baked leftover from my experiments to show worker animations when they despawn, rather than simply disappearing. Because the camera was always on the tile when I finalized the code I hadn't realized I'd even left it.

Thanks for catching that - removed now.

Copy link
Copy Markdown
Contributor Author

@instafluff0 instafluff0 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please see comments inline regarding Map::calc_commerce_yield_at.

Comment thread injected_code.c Outdated
}
}

if (is->current_config.enable_districts) {
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right. Short answer: I got a crash every time I tried to patch Map::calc_commerce_yield_at.

If I remove this block and add:

inlead, 0x5d7AD0, 0x0, 0x0, "Map_calc_commerce_yield_at", "int (__fastcall *) (Map * this, int edx, int tile_x, int tile_y)"

int __fastcall
patch_Map_calc_commerce_yield_at (Map * this, int edx, int tile_x, int tile_y)
{
	if (! is->current_config.enable_districts)
		return Map_calc_commerce_yield_at (this, __, tile_x, tile_y);

	Tile * tile = tile_at (tile_x, tile_y);
	if ((tile != NULL) && (tile != p_null_tile)) {
		struct district_instance * inst = get_district_instance (tile);
		if (inst != NULL &&
		    district_is_complete (tile, inst->district_id)) {
			if (! district_uses_tile_improvement_rules (inst->district_id))
				return 0;
			City * yield_city = get_city_ptr (tile->Body.CityAreaID);
			if (! district_tile_bonus_applies_to_city (tile, inst->district_id, yield_city))
				return 0;
			struct district_config * cfg = &is->district_configs[inst->district_id];
			int commerce_bonus = 0;
			get_effective_district_yields (inst, cfg, NULL, NULL, &commerce_bonus, NULL, NULL, NULL);
			if ((cfg->generated_resource_id >= 0) &&
			    (cfg->generated_resource_flags & MF_YIELDS) &&
			    district_generates_resource_for_civ (tile, inst, cfg, yield_city->Body.CivID)) {
				Resource_Type * res = &p_bic_data->ResourceTypes[cfg->generated_resource_id];
				commerce_bonus += res->Commerce;
			}
			return commerce_bonus;
		}
	}

	return Map_calc_commerce_yield_at (this, __, tile_x, tile_y);
}

I get a crash upon founding a city or trying to open the city screen. I could not figure out why and probably should have tried to determine if the arguments we had for that function in Ghidra were correct, but did not, so this seemed a reasonable workaround. But I hear you and agree it is duplicative. I think my development of this branch also pre-dated our earlier discussion of how to figure this stuff out using Ghidra.

Let me take another stab at it.

Comment thread injected_code.c Outdated
}
}

if (is->current_config.enable_districts) {
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Update: I believe I've got things running now and corrected. Ghidra had RET 0x18 for Map::calc_commerce_yield_at, so 6 args, which I'm fairly certain were exactly the same as Map::calc_food_yield_at.

I updated civ_prog_objects accordingly, removed the block in patch_City_calc_tile_yield_while_gathering, and added patch_Map_calc_commerce_yield_at.

@maxpetul
Copy link
Copy Markdown
Owner

maxpetul commented Jun 4, 2026

Great, that all looks good now. There's one little cosmetic thing I just noticed: the commerce gathered from improvement-type districts is not outlined in purple in the "COMMERCE" area of the lower frame of the city screen. That's unlike shields from improv-type districts and commerce from normal-type ones, which are both outlined in purple.

And that reminds me of another cosmetic thing I didn't mention before: when you hover over an improv-type district that's being worked on the city screen, the game doesn't highlight the tile yields like it normally does. I figured you were aware of that and decided it wasn't worth bothering with, but I'm pointing it out just in case. I'm not insisting you change it, if you really think it's not worth the effort, I'm fine merging this PR as is.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants