Skip to content

Weather

Allofich edited this page Apr 27, 2024 · 22 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

short AngleMultipliers[641]; // @466A6

// Called from the fog function, as well as many other functions in the game 
void FUN_1000_937e(int angle, short &var1, short &var2) {
    int doubled_var1 = var1 * 2;
    int doubled_var2 = var2 * 2;

    int imul_res1 = doubled_var1 * AngleMultipliers[angle + 128];
    int high_res1 = (unsigned int)imul_res1 >> 16;

    int multiplier1 = AngleMultipliers[angle];
    int neg_multiplier1 = -multiplier1;
    if (neg_multiplier1 >= 0) {
        neg_multiplier1--;
    }
    
    int imul_res2 = doubled_var2 * neg_multiplier1;
    int high_res2 = (unsigned int)imul_res2 >> 16;

    var1 = high_res2 + high_res1;

    int imul_res3 = doubled_var1 * AngleMultipliers[angle];
    int high_res3 = (unsigned int)imul_res3 >> 16;

    int imul_res4 = doubled_var2 * AngleMultipliers[angle + 128];
    int high_res4 = (unsigned int)imul_res4 >> 16;

    var2 = high_res3 + high_res4;

    return;
}

short WORD_ARRAY_4b80_81d8[24]; // @439D8. Both read from and written to.

int DWORD_4b80_819a = 0;
int DWORD_4b80_819e = 0;
int DWORD_4b80_81a2 = 0;
int DWORD_4b80_81a6 = 0x6906904; // Constant value. @439A6.
int DWORD_4b80_81aa;

short WORD_4b80_81ae = 0;
short WORD_4b80_81b0 = 0;
short WORD_4b80_81b2 = 0;
short WORD_4b80_81b4 = 0;
short WORD_4b80_81b6 = 0;
short WORD_4b80_81b8 = 0;
short WORD_4b80_81c6 = 0;
short WORD_4b80_81c8 = 0;
short WORD_4b80_81ca = 0;
short WORD_4b80_81d4 = 0xFC00; // Constant value. @439D4.
short WORD_4b80_8208 = 0;

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

short PlayerX;
short WORD_4b80_191b = 4; // Initially 0, it will be 4 by the first call of this function, and +4 every time after (every game update).
short PlayerY;
short WORD_4b80_191d = 4; // Initially 0, it will be 4 by the first call of this function, and +4 every time after (every game update).
short PlayerAngle;

// Called once every game world update
int SampleFOGTXT()
{
    // First the file FOG.TXT is loaded into memory if it hasn't been already. Omitted here.

    short AX;
    short BX;
    short CX;
    short DI;
    short DX;
    short BP;
    
    int intValue; // Used here for 32-bit operations
    int intValue2; // Used here for 32-bit operations
    int intValue3; // Used here for 32-bit operations
    long long longValue; // Used here for 64-bit operations
    int index;
    
    int loopCount = 4;
    index = 0;
    
    do {
    AX = WORD_ARRAY_4b80_81d8[index];
    CX = WORD_ARRAY_4b80_81d8[index+2];
    DI = 511;
    DI -= PlayerAngle; // PlayerAngle is never greater than 511
    
    FUN_1000_937e(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;
    BP >>= 3;
    
    AX = WORD_ARRAY_4b80_81d8[15];
    AX -= WORD_ARRAY_4b80_81d8[3];
    
    intValue = AX * WORD_4b80_8208;
    AX = intValue;
    DX = intValue >> 16;
    
    intValue = AX / BP;
    DX = intValue >> 16;
    
    AX += WORD_ARRAY_4b80_81d8[3];
    WORD_4b80_81b0 = AX;
    
    AX = WORD_ARRAY_4b80_81d8[16];
    AX -= WORD_ARRAY_4b80_81d8[4];
    
    intValue = AX * WORD_4b80_8208;
    AX = intValue;
    DX = intValue >> 16;
    
    intValue = AX / BP;
    DX = intValue >> 16;
    
    AX += WORD_ARRAY_4b80_81d8[4];
    WORD_4b80_81b4 = AX;
    
    AX = WORD_ARRAY_4b80_81d8[17];
    AX -= WORD_ARRAY_4b80_81d8[5];
    
    intValue = AX * WORD_4b80_8208;
    AX = intValue;
    DX = intValue >> 16;
    
    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];
    
    intValue = AX * WORD_4b80_8208;
    AX = intValue;
    
    AX += WORD_ARRAY_4b80_81d8[9];
    WORD_4b80_81c6 = AX;
    
    AX = WORD_ARRAY_4b80_81d8[22];
    AX -= WORD_ARRAY_4b80_81d8[10];
    
    intValue = AX * WORD_4b80_8208;
    AX = intValue;
    
    AX += WORD_ARRAY_4b80_81d8[10];
    WORD_4b80_81c8 = AX;
    
    AX = WORD_ARRAY_4b80_81d8[23];
    AX -= WORD_ARRAY_4b80_81d8[11];
    
    intValue = AX * WORD_4b80_8208;
    AX = intValue;
    
    AX += WORD_ARRAY_4b80_81d8[11];
    WORD_4b80_81ca = AX;
    
    BP = 39;
    
    AX = WORD_4b80_81c6;
    AX -= WORD_4b80_81b0;
    longValue = AX << 0x10;
    longValue /= BP;
    DWORD_VALUE3 = longValue;
    
    AX = WORD_4b80_81c8;
    AX -= WORD_4b80_81b4;
    longValue = AX << 0x10;
    longValue /= BP;
    DWORD_VALUE4 = longValue;
    
    AX = WORD_4b80_81ca;
    AX -= WORD_4b80_81b8;
    longValue = AX << 0x10;
    longValue /= BP;
    DWORD_VALUE5 = longValue;
    
    intValue = WORD_4b80_81b4 * WORD_4b80_81d4;
    
    DWORD_4b80_819e = intValue2;
    
    AX = WORD_4b80_81c8;
    intValue = AX * WORD_4b80_81d4;
    intValue2 -= intValue;

    longValue = intValue2 * DWORD_4b80_81a6;
    
    intValue = longValue;
    intValue2 = longValue >> 32;
    
    DWORD_VALUE1 = intValue;
    DWORD_VALUE2 = intValue2;
    DWORD_4b80_81a2 = 0;
    
    loopCount = 40;
    
    do {
    longValue = DWORD_4b80_819a;
    intValue = DWORD_4b80_819e;
    if (intValue != 0){
        longValue /= intValue;
        intValue = longValue;
        if (intValue < 0){ // untested
            intValue *= DWORD_4b80_81aa;
            intValue2 = ((unsigned int)intValue2 >> 31) | ((unsigned int)intValue << 1);
        }
        
        intValue3 = intValue; // Store for using below
        
        longValue = intValue * (long long)intValue * (long long)((unsigned short)WORD_4b80_81ae | ((unsigned short)WORD_4b80_81b0 << 16));
        intValue = longValue;
        intValue2 = longValue >> 32;
        intValue = ((unsigned int)intValue >> 24) | ((unsigned int)intValue2 << 8);
        intValue += PlayerX + WORD_4b80_191b;
        intValue = (unsigned int)intValue >> 6;
        
        longValue = (long long)intValue3 * (long long)((unsigned short)WORD_4b80_81b6 | ((unsigned short)WORD_4b80_81b8 << 16));
        intValue2 = longValue;
        intValue3 = longValue >> 32;
        intValue2 = ((unsigned int)intValue2 >> 24) | ((unsigned int)intValue3 << 8);
        
        intValue2 += PlayerY + WORD_4b80_191d;
        intValue2 = (unsigned int)intValue2 >> 6;
        
        intValue &= 0x7f;
        intValue <<= 7;
        
        intValue2 &= 0x7f;
        
        intValue += intValue2;
        intValue <<= 1;
        
        AX = FOGTXT[intValue]; // Get 2-byte value from data read from FOG.TXT
    }
    else { // untested
        AX = 0x0C00;        
    }
    
    //Store the value to the sample buffer and move the pointer to the buffer forward 2 bytes to prepare for the next iteration

    //Get CF for ADC instruction
    bool carry = false;
    unsigned int temp = (unsigned int)DWORD_4b80_81a2 + (unsigned int)DWORD_VALUE1;
    if (temp < DWORD_4b80_81a2) {
        carry = true; // overflow occurred
    }
    DWORD_4b80_81a2 = temp;   // Store the result back

    DWORD_4b80_819e += DWORD_VALUE2;
    if (carry) {
         DWORD_4b80_819e++;  // ADC instruction, so add carry flag
    }

    WORD_4b80_81ae += (DWORD_VALUE3 & 0x0000FFFF);
    WORD_4b80_81b0 += (DWORD_VALUE3 & 0xFFFF0000) >> 16;
    WORD_4b80_81b2 += (DWORD_VALUE4 & 0x0000FFFF);
    WORD_4b80_81b4 += (DWORD_VALUE4 & 0xFFFF0000) >> 16;
    WORD_4b80_81b6 += (DWORD_VALUE5 & 0x0000FFFF);
    WORD_4b80_81b8 += (DWORD_VALUE5 & 0xFFFF0000) >> 16;
        
    loopCount--;
    } while (loopCount != 0);
    
    WORD_4b80_8208++;
  } while (WORD_4b80_8208 != 25);

    return;
}

Immediately after the FOG.TXT sampling function, the following function is called which uses the gathered data. The data from FOG.LGT is also used here without alterations, so it needs to have been loaded into memory first.

short WORD_4b80_81ae = 0x533C; // This is variable. In testing was 0x533C, which matched the location (533C:0000) given here as ESArray.
short WORD_4b80_a784 = 0x92; // Variable, but might always be 0x92 when fog functions called
short FOGTXTSample[130]; // actual size unknown
char FOGLGT[1663]; // actual size unknown, but this covers all of FOG.LGT
char ESArray[320]; // Unknown, but presumably for the 320 columns of pixels on the screen

#define ApplyNewData \
    do { \
        BX += DX; \
        CX = (CX & 0xFF) | ((BX & 0xFF) << 8); \
        BX = (BX & 0xFF00) | ESArray[DI]; \
        AX = FOGLGT[BX]; \
        BX = (CX & 0xFF00) >> 8; \
        DX += BP; \
        BX += DX; \
        CX = (CX & 0xFF) | ((BX & 0xFF) << 8); \
        BX = (BX & 0xFF00) | ESArray[DI + 1]; \
        AX = (AX & 0xFF) | (FOGLGT[BX] << 8); \
        ESArray[DI] = AX & 0xFF00; \
        ESArray[DI+1] = AX & 0xFF; \
        DI += 2; \
        BX = (CX & 0xFF00) >> 8;\
        DX += BP; \
    } while(0)

#define IterateOverData \
    do { \
        ApplyNewData; \
        ApplyNewData; \
        ApplyNewData; \
        ApplyNewData; \
        SI++; \
        DX = FOGTXTSample[SI]; \
        BP = FOGTXTSample[SI+1]; \
        BP -= DX; \
        BP >>= 3; \
        FOGTXTSample[SI] = DX + FOGTXTSample[SI - 45]; \
    } while(0)

void ApplySampledFogData()
{
    short AX;
    short BX;
    short CX;
    short DI;
    short DX;
    short BP;
    short SI;
    short DS;
    short ES;
    
    for (int i = 0; i < 40; i++ ){
        FOGTXTSample[405 + i] = 0;
    }

    FOGTXTSample[28] = WORD_4b80_81ae;
    FOGTXTSample[25] = (WORD_4b80_a784 + 7) >> 3;
    FOGTXTSample[26] = 170;
    FOGTXTSample[27] = 0;
    
    do {
        for (int i = 0; i < 40; i++ ){
            FOGTXTSample[0 + i] = (FOGTXTSample[85 + i] - FOGTXTSample[45 + i]) >> 3;
        }
    
        DS = 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;
            DI = 0;
            DX = FOGTXTSample[SI / 2];
            BP = (FOGTXTSample[(SI + 2) / 2] - DX) >> 3;
            FOGTXTSample[SI / 2] = DX + FOGTXTSample[0];
    
            for (int i = 0; i < 39; i++){
                IterateOverData;
            }
    
            ApplyNewData;
            SI++;
            CX--;
        } while (CX != 0);
    
        FOGTXTSample[42] = DI;
        FOGTXTSample[40]--;
    } while (FOGTXTSample[40] != 0);

    return ;
}
Clone this wiki locally