Skip to content

Weather

Allofich edited this page Oct 1, 2025 · 52 revisions

The weather is specific to each of 36 quadrants of the provinces, and changes every hour.

Weather calculation

season <- getSeason() # March = Spring = 0
for i <- 0, 36
   climate <- climates[i]
   variant <- <40% for 2, 20% for 1 and 3, 10% for 0 or 4>
   weather[i] <- weatherTable[climate*20 + season*5 + variant]

climates is a 36 1-byte values @4295E

weatherTable is 140 1-byte values @42990

Value mapping

0  clear
1  overcast, fog
2  rain
3  snow
4  snow overcast, fog
5  rain
6  same as 1?
7  snow overcast, if rnd()<16000 then fog (recalculate on each outside transition)

If rain and rnd()<24000, it is thunderstorm.

If fog and (day number & 8), it is heavy fog.

In terrain type 3 (different from climate above), there is raining instead of snow/snow overcast.

Rain in cities uses W tileset. Overcast and rain in the wilderness use R tileset.

When it is snowing or snow overcast, the following NPCs are missing from cities: fire-eater, prostitutes, and snake enchanter (bugged in the original game).

Lightning

Lightning bolts and sky flashes can occur >6:01pm and <6:00am.

chance <- rnd()
if chance < 2000 then
   lightning <- chance mod 6
   angle <- rnd() mod 512
   x,y <- polar_to_xy(10000, angle)
   # this happens for next 3 frames
   paint_sky(flash_color[frame])
   X,Y <- project_to_screen(x,y)
   blit(lightnings[lightning],X,Y)
   # when done, do thunder
   play_sound(28, player_position)
endif

flash_colors is a 3-byte array @43602

lightnings is an image array of lglit01.cfa, lglit02.cfa, etc.

Fog

int16_t CosineTable[641]; // @4A3D6

void RotatePoint(int16_t angle, int16_t& xPos, int16_t& zPos) {
	int16_t doubled_xPos = xPos * 2;
	int16_t doubled_zPos = zPos * 2;

	int32_t imul_res1 = doubled_xPos * CosineTable[angle + 128];
	int16_t high_res1 = imul_res1 >> 16;

	int16_t multiplier1 = CosineTable[angle];
	int16_t neg_multiplier1 = -multiplier1;

	bool overflow = (multiplier1 == INT16_MIN);
	if (overflow) {
		neg_multiplier1--;
	}

	int32_t imul_res2 = doubled_zPos * neg_multiplier1;
	int16_t high_res2 = imul_res2 >> 16;

	xPos = high_res2 + high_res1;

	int32_t imul_res3 = doubled_xPos * CosineTable[angle];
	int16_t high_res3 = imul_res3 >> 16;

	int32_t imul_res4 = doubled_zPos * CosineTable[angle + 128];
	int16_t high_res4 = imul_res4 >> 16;

	zPos = high_res3 + high_res4;
}

int16_t WORD_ARRAY_4b80_81d8[24]; // @47708. Both read from and written to.

int32_t DWORD_4b80_819a = 0;
int32_t DWORD_4b80_819e = 0;
int32_t DWORD_4b80_81a2 = 0;
int32_t DWORD_4b80_81a6 = 0x6906904; // Constant value. @476D6.
int32_t DWORD_4b80_81aa;

uint16_t WORD_4b80_81ae = 0;
uint16_t WORD_4b80_81b0 = 0;
int16_t WORD_4b80_81b2 = 0;
int16_t WORD_4b80_81b4 = 0;
uint16_t WORD_4b80_81b6 = 0;
uint16_t WORD_4b80_81b8 = 0;
int16_t WORD_4b80_81c6 = 0;
int16_t WORD_4b80_81c8 = 0;
int16_t WORD_4b80_81ca = 0;
int16_t WORD_4b80_81d4 = 0xFC00; // Constant value. @47704.
int16_t WORD_4b80_8208 = 0;

int16_t WORD_4b80_a784 = 0x92; // Variable value, but might always be 0x92 when this function is called.
int32_t DWORD_VALUE1;
int32_t DWORD_VALUE2;
int32_t DWORD_VALUE3;
int32_t DWORD_VALUE4;
int32_t DWORD_VALUE5;

int16_t PlayerX;
int16_t PlayerZ;
int16_t PlayerAngle;
uint16_t WORD_4b80_191b = 0;
uint16_t WORD_4b80_191d = 0;

int16_t FOGTXTSample[1045] = { 0 };

void SampleFOGTXT()
{
	WORD_4b80_191b += 4;
	WORD_4b80_191d += 4;

	// First the file FOG.TXT is loaded into memory if it hasn't been already. Omitted here.
	int32_t index = 0;
	int32_t loopCount = 4;
	int32_t FOGTXTSampleIndex = 45;

	do {
		AX = WORD_ARRAY_4b80_81d8[index];
		CX = WORD_ARRAY_4b80_81d8[index + 2];
		DI = 511;
		DI -= PlayerAngle; // PlayerAngle is never greater than 511

		RotatePoint(DI, AX, CX);

		BX = WORD_ARRAY_4b80_81d8[index + 1];
		WORD_ARRAY_4b80_81d8[index + 3] = AX;

		WORD_ARRAY_4b80_81d8[index + 4] = BX;
		WORD_ARRAY_4b80_81d8[index + 5] = CX;
		index += 6;
		loopCount--;
	} while (loopCount != 0);

	DWORD_4b80_819a = 0xD0300000; // Original game does a few calculations here to get the value, but it will always be this result
	DWORD_4b80_81aa = 0xDD5D5D5E; // Original game does a few calculations here to get the value, but it will always be this result
	WORD_4b80_8208 = 0;

	do {
		BP = WORD_4b80_a784; // Seems to always be 0092 when this function is called
		BP >>= 3;

		AX = WORD_ARRAY_4b80_81d8[15];
		AX -= WORD_ARRAY_4b80_81d8[3];

		int32_t product = AX * WORD_4b80_8208;
		AX = static_cast<int16_t>(product);
		DX = product >> 16;

		int32_t dividend = (DX << 16) | AX;
		int16_t divisor = BP;

		AX = static_cast<int16_t>(dividend / divisor);
		DX = dividend % divisor;

		AX += WORD_ARRAY_4b80_81d8[3];
		WORD_4b80_81b0 = AX;

		AX = WORD_ARRAY_4b80_81d8[16];
		AX -= WORD_ARRAY_4b80_81d8[4];

		product = AX * WORD_4b80_8208;
		AX = static_cast<int16_t>(product);
		DX = product >> 16;

		dividend = (DX << 16) | AX;
		divisor = BP;

		AX = static_cast<int16_t>(dividend / divisor);
		DX = dividend % divisor;

		AX += WORD_ARRAY_4b80_81d8[4];
		WORD_4b80_81b4 = AX;

		AX = WORD_ARRAY_4b80_81d8[17];
		AX -= WORD_ARRAY_4b80_81d8[5];

		product = AX * WORD_4b80_8208;
		AX = static_cast<int16_t>(product);
		DX = product >> 16;

		dividend = (DX << 16) | AX;
		divisor = BP;

		AX = static_cast<int16_t>(dividend / divisor);
		DX = dividend % divisor;

		AX += WORD_ARRAY_4b80_81d8[5];
		WORD_4b80_81b8 = AX;

		WORD_4b80_81ae = 0;
		WORD_4b80_81b2 = 0;
		WORD_4b80_81b6 = 0;

		AX = WORD_ARRAY_4b80_81d8[21];
		AX -= WORD_ARRAY_4b80_81d8[9];

		product = AX * WORD_4b80_8208;
		AX = static_cast<int16_t>(product);
		DX = product >> 16;

		dividend = (DX << 16) | AX;
		divisor = BP;

		AX = static_cast<int16_t>(dividend / divisor);
		DX = dividend % divisor;

		AX += WORD_ARRAY_4b80_81d8[9];
		WORD_4b80_81c6 = AX;

		AX = WORD_ARRAY_4b80_81d8[22];
		AX -= WORD_ARRAY_4b80_81d8[10];

		product = AX * WORD_4b80_8208;
		AX = static_cast<int16_t>(product);
		DX = product >> 16;

		dividend = (DX << 16) | AX;
		divisor = BP;

		AX = static_cast<int16_t>(dividend / divisor);
		DX = dividend % divisor;

		AX += WORD_ARRAY_4b80_81d8[10];
		WORD_4b80_81c8 = AX;

		AX = WORD_ARRAY_4b80_81d8[23];
		AX -= WORD_ARRAY_4b80_81d8[11];

		product = AX * WORD_4b80_8208;
		AX = static_cast<int16_t>(product);
		DX = product >> 16;

		dividend = (DX << 16) | AX;
		divisor = BP;

		AX = static_cast<int16_t>(dividend / divisor);
		DX = dividend % divisor;

		AX += WORD_ARRAY_4b80_81d8[11];
		WORD_4b80_81ca = AX;

		BP = 39;

		AX = WORD_4b80_81c6;
		AX -= WORD_4b80_81b0;
		EAX = AX << 16;

		int64_t dividend2 = EAX;
		int32_t divisor2 = BP;

		EAX = static_cast<int32_t>(dividend2 / divisor2);
		EDX = static_cast<int32_t>(dividend2 % divisor2);

		DWORD_VALUE3 = EAX;

		AX = WORD_4b80_81c8;
		AX -= WORD_4b80_81b4;
		EAX = AX << 16;

		dividend2 = EAX;
		divisor2 = BP;

		EAX = static_cast<int32_t>(dividend2 / divisor2);
		EDX = static_cast<int32_t>(dividend2 % divisor2);

		DWORD_VALUE4 = EAX;

		AX = WORD_4b80_81ca;
		AX -= WORD_4b80_81b8;
		EAX = AX << 16;

		dividend2 = EAX;
		divisor2 = BP;

		EAX = static_cast<int32_t>(dividend2 / divisor2);
		EDX = static_cast<int32_t>(dividend2 % divisor2);

		DWORD_VALUE5 = EAX;

		ECX = WORD_4b80_81b4 * WORD_4b80_81d4;
		DWORD_4b80_819e = ECX;

		EAX = WORD_4b80_81c8 * WORD_4b80_81d4;
		EAX -= ECX;

		int64_t product2 = static_cast<int64_t>(EAX) * static_cast<int64_t>(DWORD_4b80_81a6);
		EAX = static_cast<int32_t>(product2);
		EDX = product2 >> 32;

		DWORD_VALUE1 = EAX;
		DWORD_VALUE2 = EDX;
		DWORD_4b80_81a2 = 0;

		loopCount = 40;

		do {
			EAX = DWORD_4b80_819a;
			EBP = DWORD_4b80_819e;
			if (EBP != 0) {

				dividend2 = EAX;
				divisor2 = EBP;

				EAX = static_cast<int32_t>(dividend2 / divisor2);
				EDX = dividend2 % divisor2;

				if (EAX < 0) {
					product2 = static_cast<int64_t>(EAX) * static_cast<int64_t>(DWORD_4b80_81aa);
					EAX = static_cast<int32_t>(product2);
					EDX = product2 >> 32;
					EAX = (static_cast<uint32_t>(EAX) >> 31) | (EDX << 1);
				}

				EBX = EAX;
				EBP = WORD_4b80_81ae | (WORD_4b80_81b0 << 16);

				product2 = static_cast<int64_t>(EAX) * static_cast<int64_t>(EBP);
				EAX = static_cast<int32_t>(product2);
				EDX = product2 >> 32;

				EAX = (static_cast<uint32_t>(EAX) >> 24) | (EDX << 8);
				EAX += PlayerX + WORD_4b80_191b;
				EAX >>= 6;
				std::swap(EAX, EBX);

				EBP = WORD_4b80_81b6 | (WORD_4b80_81b8 << 16);

				product2 = static_cast<int64_t>(EAX) * static_cast<int64_t>(EBP);
				EAX = static_cast<int32_t>(product2);
				EDX = product2 >> 32;

				EAX = (static_cast<uint32_t>(EAX) >> 24) | (EDX << 8);
				EAX += PlayerZ + WORD_4b80_191d;
				EAX >>= 6;

				BX = static_cast<int16_t>(EBX);
				BX &= 0x7F;
				BX <<= 7;

				AX = static_cast<int16_t>(EAX);
				AX &= 0x7F;

				BX += AX;

				AX = FOGTXT[BX];
			}
			else {
				AX = (EAX & 0x00FF) | 0x0C00;
			}

			// Write the value to the sample buffer FOGTXTSample, a short value array, at FOGTXTSampleIndex.
			FOGTXTSample[FOGTXTSampleIndex] = AX; // Write the calculated value
			FOGTXTSampleIndex++;

			// Next is, in assembly:
			// ADD dword[81a2], DWORD_VALUE1
			// ADC dword[819e], DWORD_VALUE2
			// ADD dword[81ae], DWORD_VALUE3
			// ADD dword[81b2], DWORD_VALUE4
			// ADD dword[81b6], DWORD_VALUE5

			// The ADC instruction adds any carry from the preceding ADD instruction, so we need to get the carry
			bool carry = false;
			uint32_t before = DWORD_4b80_81a2;
			DWORD_4b80_81a2 += DWORD_VALUE1;
			if (DWORD_4b80_81a2 < before) {
				carry = true; // overflow occurred
			}

			DWORD_4b80_819e += DWORD_VALUE2;
			if (carry) {
				DWORD_4b80_819e++;
			}

			int sum = (WORD_4b80_81ae & 0xFFFF | ((WORD_4b80_81b0 & 0xFFFF) << 16));
			sum += DWORD_VALUE3;
			WORD_4b80_81ae = static_cast<int16_t>(sum);
			WORD_4b80_81b0 = sum >> 16;

			sum = (WORD_4b80_81b2 & 0xFFFF | ((WORD_4b80_81b4 & 0xFFFF) << 16));
			sum += DWORD_VALUE4;
			WORD_4b80_81b2 = static_cast<int16_t>(sum);
			WORD_4b80_81b4 = sum >> 16;

			sum = (WORD_4b80_81b6 & 0xFFFF | ((WORD_4b80_81b8 & 0xFFFF) << 16));
			sum += DWORD_VALUE5;
			WORD_4b80_81b6 = static_cast<int16_t>(sum);
			WORD_4b80_81b8 = sum >> 16;

			loopCount--;
		} while (loopCount != 0);

		WORD_4b80_8208++;
	} while (WORD_4b80_8208 != 25);
}

int16_t WORD_4b80_a76a = 0x533C;
int16_t WORD_4b80_a784 = 0x92; // Variable, but might always be 0x92 when fog functions called
int8_t FOGLGT[1663]; // The contents of FOG.LGT
int16_t ESArray[23360]; // For 320 columns x 146 rows of screen pixels. Already populated with the game screen when these functions are called.

// Depending on input values, BX may become out-of-range for accessing FOG.LGT. In this case the original game just uses the out-of-range data.
inline void ApplyNewData() {
	BX += DX;
	CX = static_cast<int16_t>((CX & 0xFF) | ((BX & 0xFF) << 8));
	BX = (BX & 0xFF00) | (ESArray[DI / 2] & 0xFF);
	AX = static_cast<int16_t>((AX & 0xFF00) | FOGLGT[BX]);
	BX = (CX & 0xFF00) >> 8;
	DX += BP;
	BX += DX;
	CX = static_cast<int16_t>((CX & 0xFF) | ((BX & 0xFF) << 8));
	BX = (BX & 0xFF00) | (ESArray[DI / 2] & 0xFF00) >> 8;
	AX = static_cast<int16_t>((AX & 0xFF) | (FOGLGT[BX] << 8));
	ESArray[DI / 2] = AX;
	DI += 2;
	BX = (CX & 0xFF00) >> 8;
	DX += BP;
}

inline void IterateOverData() {
	DX = FOGTXTSample[SI / 2];
	BP = FOGTXTSample[(SI + 2) / 2];
	BP -= DX;
	BP >>= 3;
	FOGTXTSample[SI / 2] = DX + FOGTXTSample[(SI - (FOGTXTSample[41] - 80)) / 2];
	ApplyNewData();
	ApplyNewData();
	ApplyNewData();
	ApplyNewData();
	SI++;
	SI++;
}

static void ApplySampledFogData()
{
	for (int32_t i = 0; i < 40; i++) {
		FOGTXTSample[405 + i] = 0;
	}

	FOGTXTSample[43] = WORD_4b80_a76a;
	FOGTXTSample[40] = (WORD_4b80_a784 + 7) >> 3;
	FOGTXTSample[41] = 90;
	FOGTXTSample[42] = 0;

	do {
		SI = FOGTXTSample[41] + 80;
		FOGTXTSample[41] = SI;
		for (int32_t i = 0; i < 40; i++) {
			FOGTXTSample[0 + i] = (FOGTXTSample[(SI / 2) + i] - FOGTXTSample[(SI / 2) - 40 + i]) >> 3;
		}

		DI = FOGTXTSample[42];
		ES = FOGTXTSample[43]; // 0x533C in testing, used for location of ESArray
		CX = 8;

		if (FOGTXTSample[40] == 1) {
			CX -= 6;
		}

		do {
			SI = FOGTXTSample[41] - 80;
			BX = 0;

			for (int32_t i = 0; i < 40; i++) {
				IterateOverData();
			}

			CL = (CX & 0xFF);
			CL--;
			CX = static_cast<int16_t>((CX & 0xFF00) | CL);
		} while (CL != 0);

		FOGTXTSample[42] = DI;
		FOGTXTSample[40]--;
	} while (FOGTXTSample[40] != 0);
}

Every game tick, if foggy weather is active, do:

WORD_4b80_191b += 4;
WORD_4b80_191d += 4;

SampleFOGTXT();
ApplySampledFogData();

Clone this wiki locally