Show Posts

This section allows you to view all posts made by this member. Note that you can only see posts made in areas you currently have access to.


Messages - Nikita_Sadkov

Pages: 1 [2] 3 4 ... 20
16
Programming / Re: Magic & Mayhem Engine Open Source Clone
« on: March 31, 2020, 08:05:29 pm »
No way; that's ridiculous… sorry to hear that. I did wonder if it had been banned due to some kind of copyright issue but that's just depressing. I was going to ask about re-uploading it to my own profile but I understand if you would prefer I didn't.

Anyway, thank you very much for sharing your work. After being put on lockdown I re-installed Magic & Mayhem for old time's sake and felt like seeing if I could tinker with any of the game files. I've only recently become familiar with reading files at the byte-level as I primarily work with front-end tech, so this will be incredibly helpful to further my understanding of low-level programming.

Thanks again, and take care.
No. I haven't got a single copyright complaint at github or youtube, despite that SAU project reverse engineering formats of several games. In my Youtube videos I also used some copyrighted videogame content - not a single strike. Yet I got countless politically related strikes for all kinds of sensitive issues, from animal cruelty to invasion of privacy, which resulted in a ban at Vimeo, for a video of PrivatBank employees refusing to speak Ukrainian, but speaking Russian. PrivatBank did everything to take that video down from everywhere and threatened me with police.

So I guess you will be totally okay with reverse engineering or modding. Just don't claim decompiled code as your own. Although I heard people were actually selling Half Life 2 and Skyrim mods, and both Valve and Bethesda allowed that. Some Japanese musicians also sell albums of video game music interpretations. Square-Enix for now closed eyes on that.

17
Programming / Re: Magic & Mayhem Engine Open Source Clone
« on: March 31, 2020, 05:46:41 pm »
Just came across this thread after searching online and am gutted to see these GitHub links are no longer available. Would you kindly reconsider uploading the source?
Yeah. The github got banned to due to the one repository having a WIP version of a book touching US immigration policy. Apparently a lot of people don't support open borders and they mass reported me. GitHub then just banned me outright, instead of that single repository, to avoid any controversy.

Anyway, here is the code for LZ77 unpacker and the sprite format handling routines.

Code: [Select]
// Magic & Mayhem decryption and unpacking routines
// Author: Nikita Sadkov
// License: BSD
// Decscription:
//   Routines used to unpack *.cfg and *.map files.

#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>

#define HIDWORD(x)  (*((uint32_t*)&(x)+1))

#define COMP_PLAIN  0
#define COMP_RLE    1
#define COMP_LZ77   2

#define HEADER_SIZE 20

typedef struct {
  uint32_t Seed; //seed for the PRNG used in decryption
  uint32_t UnpackedSize; //size of data[] after decompression
  uint32_t Checksum1; //checksum of decrypted data[]
  uint32_t Checksum2; //checksum of uncompressed data[]
  uint32_t Compression; //0=plain-text, 1=RLE, 2=LZ77
} __attribute__ ((packed)) header;

int filesize(char *filename) {
  int Size;
  FILE *F = fopen(filename, "rb");
  if (!F) return -1;
 
  fseek(F, 0L, SEEK_END);
  Size = ftell(F);
  fseek(F, 0L, SEEK_SET);
  fclose(F);

  return Size;
}

uint32_t PRNG_State;
uint32_t PRNG_Map[256];
int PRNG_MapReady = 0;

void init_prng_map() {
  int i;
  for (i = 0; i < 0xF9; i++) PRNG_Map[i] = i+1;
  PRNG_Map[0xF9] = 0;
}

uint32_t prng(uint32_t *table) {
  uint32_t a, b, c;
  a = table[0];
  table[0] = PRNG_Map[a];
  b = table[1];
  table[1] = PRNG_Map[b];
  c = table[b + 2] ^ table[a + 2];
  table[a + 2] = c;
  return c;
}

#define _WORD uint16_t


#define LZDictSize 4096
uint8_t LZDict[LZDictSize];

typedef struct {
  uint8_t *Ptr;
  uint8_t BitPtr;
  uint32_t Value;
} __attribute__ ((packed)) lz_input;

void lz_unpack(uint8_t *Input, uint8_t *Output, int UnpackedSize) {
  lz_input LZInput;
  lz_input *LZ;
  int Count;
  uint8_t Bit;
  uint8_t *PtrInc;
  int ValueBit;
  char NextBit;
  char Value;
  char NextBit2;
  uint32_t BackRefBit;
  int BackRefOff;
  int BackRefI;
  int BackRefLen;
  char NextBit3;
  uint32_t LowBit;
  uint32_t HighBit;
  char NextBit4;
  int Value2;
  int DictIndex;
  int CountSave;

  memset(LZDict, 0, LZDictSize);

  LZ = &LZInput;

  LZ->Ptr = Input;
  LZ->BitPtr = 0x80;
  LZ->Value = 0;

  Count = 0;
  CountSave = 0;
  DictIndex = 1;
  while ( 1 ) {
    Bit = LZ->BitPtr;
    if ( Bit == 0x80 )
    {
      PtrInc = LZ->Ptr + 1;
      LZ->Value = *LZ->Ptr;
      LZ->Ptr = PtrInc;
    }
    ValueBit = LZ->Value & Bit;
    NextBit = Bit >> 1;
    LZ->BitPtr = NextBit;
    if ( !NextBit )
      LZ->BitPtr = 0x80;
    if ( ValueBit )
    {
      HighBit = 0x80;
      Value = 0;
      do
      {
        Bit = LZ->BitPtr;
        if ( Bit == 0x80 )
        {
          PtrInc = LZ->Ptr + 1;
          LZ->Value = *LZ->Ptr;
          LZ->Ptr = PtrInc;
        }
        if ( Bit & LZ->Value )
          Value |= HighBit;
        HighBit >>= 1;
        NextBit2 = Bit >> 1;
        LZ->BitPtr = NextBit2;
        if ( !NextBit2 )
          LZ->BitPtr = 0x80;
      }
      while ( HighBit );
      *Output++ = Value;
      ++Count;
      LZDict[DictIndex] = Value;
      CountSave = Count;
      DictIndex = ((uint16_t)DictIndex + 1) & 0xFFF;
      goto loop_continue;
    }
    BackRefBit = 0x800;
    BackRefOff = 0;
    do
    {
      Bit = LZ->BitPtr;
      if ( Bit == 0x80 )
      {
        PtrInc = LZ->Ptr + 1;
        LZ->Value = *LZ->Ptr;
        LZ->Ptr = PtrInc;
      }
      if ( Bit & LZ->Value )
        BackRefOff |= BackRefBit;
      BackRefBit >>= 1;
      NextBit3 = Bit >> 1;
      LZ->BitPtr = NextBit3;
      if ( !NextBit3 )
        LZ->BitPtr = 0x80;
    } while ( BackRefBit );
    if ( !BackRefOff )
      return;
    LowBit = 8;
    BackRefLen = 0;
    do
    {
      Bit = LZ->BitPtr;
      if ( Bit == 0x80 )
      {
        PtrInc = LZ->Ptr + 1;
        LZ->Value = *LZ->Ptr;
        Count = CountSave;
        LZ->Ptr = PtrInc;
      }
      if ( Bit & LZ->Value )
        BackRefLen |= LowBit;
      LowBit >>= 1;
      NextBit4 = Bit >> 1;
      LZ->BitPtr = NextBit4;
      if ( !NextBit4 )
        LZ->BitPtr = 0x80;
    }
    while ( LowBit );
    BackRefI = 0;
    if ( BackRefLen + 1 >= 0 ) {
      do
      {
        Value2 = LZDict[((uint16_t)BackRefOff + (uint16_t)BackRefI) & 0xFFF];
        *Output++ = Value2;
        ++Count;
        CountSave = Count;
        if ( Count == UnpackedSize ) return;
        LZDict[DictIndex] = Value2;
        ++BackRefI;
        DictIndex = ((uint16_t)DictIndex + 1) & 0xFFF;
      } while (BackRefI < BackRefLen+2);
    }
loop_continue:
    if ( Count == UnpackedSize )
      return;
  }
}

void prng_init(uint32_t *table, uint32_t seed) {
  uint32_t *p;
  int i, count;
  int64_t t;
  uint32_t a;
  uint32_t k;
  int32_t *q;
  int32_t b;

  if (!PRNG_MapReady) {
    for (i = 0; i < 0xF9; i++) PRNG_Map[i] = i+1;
    PRNG_Map[0xF9] = 0;
  }

  PRNG_State = seed;
  table[0] = 0;
  table[1] = 103;
  p = table + 251;
  count = 250;
  for (i=0; i < 250; i++)
  {
    t = 0x41C64E6DULL * PRNG_State;
    HIDWORD(t) <<= 16;
    t += 0xFFFF00003039ULL;
    PRNG_State = t;
    *p = HIDWORD(t) & 0xFFFF0000 | ((uint32_t)t >> 16);
    --p;
  }
  a = 0xFFFFFFFF;
  k = 0x80000000;
  q = table + 5;
  do {
    b = *q;
    *q = k | a & b;
    q += 7;
    k >>= 1;
    a >>= 1;
  } while ( k );
}

// it seeds random number generator with a key
// then it uses generated random numbers to XOR the input
void decrypt(void *Input, int Size) {
  int I, Len;
  uint32_t Table[256];
  uint32_t *P = (uint32_t*)Input;
  memset(Table, 0, 256*sizeof(uint32_t));

  prng_init(Table, 1234567890u);
  Table[254] = 0;

  prng_init(Table, *P++);

  Len = (Size-4)/4;
  for (I=0; I<Len; I++) *P++ ^= prng(Table);

  Len = (Size-4) % 4;
  for (I=0; I<Len; I++) *((uint8_t*)P) ^= prng(Table);
}

uint32_t checksum(void *Data, int Size) {
  int I;
  uint32_t Sum=0, Elem, *P=(uint32_t*)Data;
  Size /= 4;
  for (I = 0; I < Size; I++) {
    Elem = *P++;
    if (I&1) Sum += Elem;
    else Sum ^= Elem;
  }
  return Sum;
}

int main(int argc, char **argv) {
  FILE *InFile, *OutFile;
  uint32_t *Input;
  uint8_t *Output;
  int InSize, OutSize, UnpackedSize;
  header *H;

  if (argc!=3) {
    printf("Usage: mmdecrypt <input> <output>\n");
    return 0;
  }

  InSize = filesize(argv[1]);

  if (InSize <= 20) { //cant be smaller or equal to the header
    printf("Bad file: %s\n", argv[1]);
    return 0;
  }

  InFile = fopen(argv[1], "rb");
  OutFile = fopen(argv[2], "wb");

  Input = (uint32_t*)malloc(InSize*2);
  fread(Input, 1, InSize, InFile);

  init_prng_map();
  decrypt(Input, InSize);

  H = (header*)Input;
  printf("unpacked size: %d\n", H->UnpackedSize);

  if (H->Checksum1 == checksum(H+1, InSize-sizeof(*H))) {
    UnpackedSize = H->UnpackedSize;
  } else {
    UnpackedSize = InSize;
    printf("bad checksum for decrypted data.\n");
  }

  Output = (uint8_t*)malloc(UnpackedSize*2);

  if (COMP_PLAIN == H->Compression) {
    printf("compression: uncompressed\n");
    memcpy(Output, (uint8_t*)(H+1), UnpackedSize);
  } else if (COMP_RLE == H->Compression) {
    printf("compression: RLE\n");
  } else if (COMP_LZ77 == H->Compression) {
    printf("compression: LZ77\n");
    lz_unpack((uint8_t*)(H+1), Output, UnpackedSize);
  } else {
    printf("compression: unknown (%x); aborting\n", H->Compression);
    return 0;
  }

  if (H->Checksum2 != checksum(Output, UnpackedSize)) {
    printf("bad checksum for decompressed data.\n");
  }

  fwrite(Output, 1, UnpackedSize, OutFile);

  fclose(InFile);
  fclose(OutFile);

  printf("done!\n");

  return 0;
}

Code: [Select]
//Credits go to SNV

#include "common.h"

typedef struct { // Total: 24 bytes
  u1 Id[4];     // SPR\0
  s4 Size;      // size of the whole file
  u4 U1;  // 0x4 for all creatures and tilesets
          // 0x2 for interf64.spr LOGO.spr magico64.spr magico80.spr
          //         overlay.spr scanner.spr Scrollbutt.spr
  u4 Frames;  // number of Frames
  u4 NPals;   // number of palettes
  u4 U2;  // 0 for battlebuttons.spr border.spr Buttons.spr
          //       CheckBox.spr interf64.spr magico80.spr markers.spr
          //       overlay.spr radiobuttons.spr scrollbar.spr selbut.spr
          //       slider.spr SplIcons.spr spotIcons.spr timer.spr
          // LOGO.spr, scanner.spr, Scrollbutt.spr have it 0xbf000000
          // magico64.spr has it 0xe3000000

  // u1 Palette[256*3*NPals];
  // u4 FrameOffsets[Frames];

} __attribute__ ((packed)) header;


typedef struct {
  u4 Deltas; // offset into Deltas
  u4 Pixels; // offset int Pixels
} __attribute__ ((packed)) row;


typedef struct {
  u4 Size;
  u4 W;  // width
  u4 H;  // height
  s4 CX; // center X
  s4 CY; // center Y
  u1 Name[8]; // Name of the file, this frame belongs to.
  u4 P;  // index of palette, this sprite uses
  u4 T1; // unknown offset 1
  u4 T2; // unknown offset 2

  //line Lines[Height];
  //u4 Deltas[];
  //u1 Pixels[];

  // sometimes, (when header->U1 != 2) there are two tables at the end,
  // pointed by T1 and T2. their purpose is unknown to me and without using them
  // output image is garbled.
  // their size seems has no relation to image size, being
  // somewhere between 80-84
  //u1 Table1[];
  //u1 Table2[];
} __attribute__ ((packed)) frame;


typedef struct {
  s4 X;
  s4 Y;
  u1 Name[8]; // Frame name in associated .spr file
  u4 U1;
  u4 U2;
  s4 X2;
  u4 U3;
  s4 Y2;
  u4 U4;
  u4 U5;
} __attribute__ ((packed)) aniFrame;

typedef struct {
  u1 Id[4]; // ANI\0
  u4 Size;  // size of whole file
  u4 U1;
  u4 U2;
  u4 U3;
  u4 NAnis; // number of animations
  u1 SprName[24]; // Name of associated .spr file;
  // u4 Offsets[NAnis]; // animation offsets
} __attribute__ ((packed)) aniHeader;



static void sprDecompile(char *Output, char *Input) {
  int I, J, K, X, Y, C;
  char Tmp[1024], Name[16];
  u4 *Lens, *Offs, *CU;
  int CTOffset; // Frame table offset
  char *LastName = "NIL Name, unused name";
  pic *P, *B;
  row *R;
  int L = fileSize(Input);
  header *H = (header*)readFile(0, L, Input);

  memset(Name, 0, 16);

  if (!H || memcmp(H->Id, "SPR\0", 4)) {
    printf("Invalid SPR file: %s\n", Input);
    abort();
  }

  printf("NFrames=%d NPals=%d U1=%d U2=#%x\n"
        , H->Frames, H->NPals, H->U1, H->U2);


  CTOffset = sizeof(header) + 256*3*H->NPals;

  if (H->U1 == 2) CTOffset -= 4;

  Offs = (u4*)((u1*)H+CTOffset);
  Lens = ns(u4, H->Frames);
  times (I, H->Frames) Offs[I] += CTOffset+4*H->Frames;
  times (I, H->Frames) Lens[I] = (I==H->Frames-1?H->Size:Offs[I+1])-Offs[I];

  times (I, H->Frames) {
    frame *F = (frame*)((u1*)H + Offs[I]);

    memcpy(Name, F->Name, 8);

    printf("%d,%s: O=#%x L=#%x W=%d H=%d CX=%d CY=%d P=%d, T1=#%x T2=#%x S1=%d S2=%d\n"
       , I, Name, Offs[I], Lens[I], F->W, F->H, F->CX, F->CY, F->P
       , F->T1, F->T2, F->T2 - F->T1, Lens[I]-F->T2);

    P = picNew(F->W?F->W:1, F->H?F->H:1, 8);

    if (H->NPals) {
      if (F->P >= H->NPals) F->P=0;
      u1 *Pal = (u1*)H+sizeof(header) + F->P*256*3;
      times (J, 256) {
        P->P[J*4+0] = Pal[J*3+0];
        P->P[J*4+1] = Pal[J*3+1];
        P->P[J*4+2] = Pal[J*3+2];
        P->P[J*4+3] = 0;
      }
    } else {
      times (J, 256) {
        P->P[J*4+0] = J;
        P->P[J*4+1] = J;
        P->P[J*4+2] = J;
        P->P[J*4+3] = 0;
      }
    }

    CU = ns(u4,256);

    P->K = 0x253;

    if (H->U1 == 2) L--;
    R = (row*)(F+1);

    for(Y=0; Y < F->H; Y++) {
      u1 *Deltas = (u1*)F + R[Y].Deltas;
      u1 *Pixels = (u1*)F + R[Y].Pixels;
      for(X=0, K=0; X < F->W; K=~K) {
        C = *Deltas++;
        unless (K) {X += C; continue;}
        while (C-- && X < F->W) {X++; CU[*Pixels++]++;}
      }
    }

    times (J, 256) if (!CU[J]) P->K = J;
    picClear(P, P->K);

    for(Y=0; Y < F->H; Y++) {
      u1 *Deltas = (u1*)F + R[Y].Deltas;
      u1 *Pixels = (u1*)F + R[Y].Pixels;
      for(X=0, K=0; X < F->W; K=~K) {
        C = *Deltas++;
        unless (K) {X += C; continue;}
        while (C-- && X < F->W) picPut(P,  X++, Y, *Pixels++);
      }
    }

    sprintf(Tmp, "%s/%s_%04d.png", Output, Name, I);
    pngSave(Tmp, P);
  }
}

int sprInit(format *F) {
  F->Type = FMT_ARCHIVE;
  F->Name = "spr";
  F->Description = "Magic & Mayhem sprites";
  F->Decompile = sprDecompile;
  return 1;
}


18
Offtopic / Re: Marines vs Aliens (Working Title)
« on: March 31, 2020, 03:12:57 am »
Most problems are solved, but one is still there.
You can see it in the second screenshot. My big units have a list of relative grid positions (eg. for a 2x2 it is [0,0],[1,0],[0,1],[1,1]) which are relative to the position I use for referencing the unit.
That is actually a hard problem. My game has no multi-cell units, but it has multi-cell static object, which can have arbitrary form and topology (even with holes inside). You won't believe how many bugs they produced, especially their rotations.

I had moving multi-cell units in the Warcraft 2 engine clone I made previously. That was a bit easier, since there large units were always 2x2, and they used a special large grid (i.e. their grid was separate from the 1x1 units). Just like in the original Warcraft 2. For what I remember, Stratagus engine actually implemented these large units sharing grid with the 1x1 units, and it led to numerous nasty bugs, especially with the original Warcraft 2 maps. Starcraft devs also mentioned in the interview that larger moving units were a special kind of pathfinder problem.

But in your case you do need a shared grid. Typically large units are implemented as a colony of smaller units. That uniformity allows you to reuse most of the code. For pathfinder you will have to check for each subpart, and also for its rotations (if unit is say 1x2 or 2x1 - i.e. some long alligator). Alternatively you can drop the gird outright and work with the "tightness" map, representing how tight is that point in space. Now really tight spaces can be accessible only by insects. Such map can be produced out of the grid. It is more robust and much easier to implement that the actual grid-based pathfinder (think XCOM TFTD novice vs superhuman difficulty).

This is in no way as complicated as his project is or openxcom.

Exactly my thoughts in the beginning! Back when the project had just a basic 2d map and a simple chess-like rules. Keep it up! - we need to advance the genre, since as of now it got flooded with boring Final Fantasy Tactics clones.

My suggestion is to pick some core idea and then prototyping around it until it feels right. Like they did with Faster Than Light.

And maybe some existing engine like Unity. I spent like 1/2 of the time developing the framework (like resource management and GUI), instead of the game. But my case is special since my goal is developing the programming language, not the game. With an established engine you can just use an existing GUI framework and it will have a builtin resource manager.

19
Offtopic / Re: What happened to Blizzard?
« on: March 26, 2020, 11:37:45 pm »
Another classic game got remade - Resident Evil 3. Note how they re-imagined everything and used totally different engine, instead of just snapping a hastily made free 3d camera and a bunch of blurry textures onto the original. I think Blizzard should had done the same, instead of maintaining strict compatibility with the original (which had numerous issues). And adding the original game as a bonus. And it is not like Blizzard had no money - Warcraft the movie had a huge budget and was really good as far as video game based movies go. Maybe providing some standalone tool, so users could convert old maps to the new engine. Here is the comparison with the original:

20
Offtopic / Re: XCOM Inspired Fantasy Game
« on: March 22, 2020, 10:58:38 pm »
But this map look more as grid based than voxel based as minecraft, look closely at places where floor have different height, each box are unalignment if you consider height, something that is impossible in minecraft.

Yeah. Original Tomb Raiders were grid based, and the characters movement are also grid based. So Tomb Raider and XCOM have a lot in common.

I'm sure you can modify Minecraft engine to support vertical blocks instead of cubes. That is what I did for Spell of Mastery, which originally had only cubic tiles in the early prototype. But for Minecraft that would kill the blocky aesthetics.

Regarding AStar and portals, I doubt they needed full blown AStar in Tomb Raider, since enemy AI is rather primitive. I.e. that bear wont be pursuing player over all the level geometry. Compared to tactical games, where AI must properly use everything player uses.

Then again, early strategy game developers haven't even tried to create a competitive AI. Say both Warcraft and Command & Conquer had AIs with pre-build bases, and such AIs attacked player with waves of increasing strength following pre-made path, wasting resources. So instead of being proper strategies, they were actually tower defense games, since player was encouraged to build towers to fend of suicide waves. That made sense since good AI would be unfun for the player. Imagine XCOM aliens playing competently for once, instead of running randomly like cockroaches around the map. That would have been a totally different game.

Anyway, here is the footage of the level editor CORE used, allowing creating levels quickly and with easy (compared to Quake, or the modern Unreal engine):



21
Offtopic / Re: XCOM Inspired Fantasy Game
« on: March 22, 2020, 03:46:45 am »
Recently the original Tomb Rider engine got  reverse-engineered and apparently it worked similarly to maincraft - i.e. with blocks primitives, but supported sloped surfaces. Although devs do use AStar for some reason, despite the game relaying on portals:


22
Offtopic / Re: XCOM Inspired Fantasy Game
« on: March 22, 2020, 03:16:18 am »
Implemented the castle siege map generator. Given the existing framework it is a cakewalk, but still needed additional feature - road from the city properly connecting to the castle entrance. For now there is no gate, since siege units and siege AI are not implemented. So AI will just rush through the opening. There is also a backdoor feature for now implemented as a teleporter, but in future I will likely replace it with a proper tunnel.

Teleporters were a bit tricky implement, since teleportation implies non-euclidean space, so algorithms like A-Star stop working. AI also needs some special handling, and there is a special edge case when some unit already occupies one of the teleporters ends, requiring to decide what happens when somebody teleports inside of it. BTW, that Tomb Rider game I mentioned earlier had some tricky feature called "portals", allowing to create buildings which have more space inside than it appears from the outside. Like in Morrowind and JRPGs, where small buildings have large interiors, but instead seamless. IIRC, Duke Nukem also had that portal feature.

While teleportation is cool, I think also implementing a time travel spell. Basically it would involve saving current game state and allowing jumping back and forth. Even in multiplayer games. Obviously the feature would require solving the so called ontological time paradox, either purging already collected items from the map or from the time traveler. That will have to be a top tier spell with large ingredient cost.


23
Offtopic / Re: XCOM Inspired Fantasy Game
« on: March 21, 2020, 01:42:53 am »
I had an idea for imps building rafts instead of bridges. But then there are waterfalls. And I also plan adding flow direction to the river, so it would move light objects buoyancy with the flow. These things greatly complicate raft mechanics, which would need some propulsion mechanism. So bridges are just simpler and more robust solution. The earliest game with river flow was probably Tomb Rider, where river impeded player's canoe movement. But I dont remember it carrying other items.


24
Offtopic / Re: XCOM Inspired Fantasy Game
« on: March 20, 2020, 03:51:07 pm »
Now, when city generator is functioning, It is the time to properly place different sides. Basically city fights happen when attacking player decides to plunder the city, instead of besieging the castle (castle siege happens on a different map, since castles are outside of cities). If city has a wall, then attacker will forced to do castle siege (plunder option is absent). There are for sides inside the city: citizens, unhired visiting mercenaries, defending player units (if player decides to participate in the battle) and enemy units. City's wealth is randomly placed inside buildings, and attacker is supposed to collect it. Unhired mercs are placed randomly to guard it. If a city gets plundered (i.e. citizens killed, and wealth stolen), its taxable income will get reduced for the upcoming turns.

There are several ways to place attacker units. But I think the best one would right inside the city. For now I use city's park. Defending player's units will be placed either at the entrance, tavern or the barracks. In all cases any player can just leave at any time, with the usual retreat rules applying. I.e. a thief can infiltrate city and steal some stuff, while being invisible.

With castle siege it is much easier - just place attackers around the castle.

On all generated maps there are power nodes. If any player captures all of them, the enemy player's units will start losing health.

I want all combat to resolve quickly, so on siege and party-vs-party combat attacking side will be encircling defending one. But city plundering is an optional thing. Same with wilderness exploration. So it is okay for little or no action happening in these sites.

25
Offtopic / Re: XCOM Inspired Fantasy Game
« on: March 19, 2020, 03:46:10 am »
Ok. Connected the river generator to the elevated city generator. Obviously all these generators work together. So the hardest part is merging them. It is easy to say generate building with rooms, a city plan, or a height map with hills, a sewer under the city, or a river on plains. But is a somewhat harder to connect everything seamlessly, when city gets spread over the hills, and there is a river with several waterfalls flowing down these cliffs, and also a road crossing said river. WFC algorithms can help there, but the result would be too random and hard to control. I also found the optimize C/C++ implementation of WFC rather slow. So it will be much slower if rewritten in my prototype language.

But yeah, seen holistically the random generator is probably the most complex part of the game.


26
Offtopic / Re: XCOM Inspired Fantasy Game
« on: March 17, 2020, 03:31:02 pm »
The planning doesn't always make sense, cliff edge pavement needs a special tile (otherwise it blends with the tiles below), and there is still a bug with warehouse door placement. But guess it is fine for now as a proof of concept. Still road planning needs to be addressed at some point. Like the screenshot below a road goes over a hill, and there is no alternate path around cliff.


27
Offtopic / Re: XCOM Inspired Fantasy Game
« on: March 17, 2020, 02:16:20 am »
Implemented elevated high city with stairs upward. The idea is that elevation adds a bit of tactical depth, since shooters on top get to-hit bonus and relatively safe from melee enemies below.

Wont be implementing overhang city for now, since it needs better room/house generation algorithm, where houses can match existing terrain nicely (i.e. Diablo style partitioning algorithm, with items added fitting to the rooms purpose). That is also a nice feature if you're making some god game, where player gives only general orders, and creatures build houses themselves, working with existing terrain, maybe minimizing some function.

Anyway, for now I use premade houses (similar to XCOM parts), which can be rotated and have randomizable sub parts. Initial house rotation code had a lot of bugs (especially with multi-cell objects), since I dont do usual trigonometry, but instead have various non-uniform hardcoded angles. I really need to refactor it if I ever plan about implementing 90 degrees camera rotation. These are all late stage features, when core gets working flawlessly.

Guess I can also use this code to produce a map generator for Warcraft III (long ago I've made a map generator for Warcraft II). But since Blizzard became assholes and claim ownership over all mods, I wont.




28
Offtopic / Re: What happened to Blizzard?
« on: March 14, 2020, 02:35:01 pm »
Absolutely. D2 had some MAJOR problems that you really had to work to get around, and it wasn't fun to deal with those. D3 is WAY too streamlined though, to the point that D2 is far more fun to play, and I don't think D2 is really a very good game. It was great in a lot of ways but it made so many critical mistakes. It wouldn't have been that hard for Blizzard to get accustomed to their game and learn what's wrong with it, and make a truly better sequel. But when your sequel invents more problems than it fixes, and simultaneously ignores everything about the atmosphere that made the previous game fun, to the point that a game you spent a lot more money on, had a lot more wisdom when making it, had better technology, and it's a lot worse than before, that's sad.
D2 was just bigger D1 with more everything. Although it introduce hirelings (probably inspired by Nethack pets) and more RPG elements. I personally don't think RPG elements are the core feature or roguelikes and that important at all to the genre. Recently there were several rogue likes without experience based character development. And even back then before Diablo there was ToeJam & Earl, which had was in real time similarly to Diablo and even had coop multiplayer, and all the major roguelike elements like unidentified and cursed items. Recently it was remastered:


29
Offtopic / Re: What happened to Blizzard?
« on: March 13, 2020, 12:13:34 am »
Diablo 2 lets the player grind as much as he wants by respawning everything when the player reenters the game.  As for random spawns, this mechanic prevents a player from retreating to a previously cleared level risk free to heal up or wait for blindness, confusion, etc to time out.  In Diablo 2, if the player's character gets cursed, he could just step back into a cleared area and wait the 30 seconds or so for the curse to disperse.
Yeah. Diablo botched that basic concept. I remember playing Chocobo Dungeon, which is basically a solid Rogue clone with Final Fantasy characters. The game had no food, but instead of player spends too many time on the same level a ghost appears to chase the player. Ghost was basically a very powerful enemy, although it was possible it defeat it for some reason. IIRC, similar mechanics was used Lords of the Rings for GBA, where instead of ghost they had ring wraith. Surprisingly there were no full featured LoTR roguelike (beside these GBA games), despite the Tolkien's story naturally leaning towards being implemented as a cooperative roguelike.

30
Offtopic / Re: What happened to Blizzard?
« on: March 12, 2020, 12:52:49 pm »
Checked that Omega's source code. It has some interesting features, like when player digs too much ground the dungeon collapses, killing player, unless player has shadowform status. Diablo never had digging, because implementing digging requires more complex tiling and dungeon generation algorithms. Diablo also had no overworld, instead Diablo II broke game into chapters, each happening in a different biome. Omega also has hunger meter, requiring player to carry rations. That was the thing with some early commercial RPGs like Might & Magic, Realms of Arkania and Betrayal in Antara, where food was rather a gimmick. IIRC, D&D also had it, but all computer D&D games omitted it.

Pages: 1 [2] 3 4 ... 20