Hurray! Another multiplayer clone project. :)

Discuss and distribute tools and methods for modding. Moderator - Grognak
jrb00001
Posts: 201
Joined: Fri Jan 15, 2016 2:22 pm

Re: Hurray! Another multiplayer clone project. :)

Postby jrb00001 » Sun Feb 07, 2016 10:41 pm

kcd.Spektor wrote:Well I'm stuck. :|
With the oxygen algorithm.
I'm probably missing some small detail but sill - I'm stuck.
Anyone want's to help me with that?

How can we help if you do not tell us what the problem is?

Does it work in one direction but not in the opposite direction? That was my first problem with this type of algorithm. If it is something else, please describe what works and what not.

stylesrj wrote:Not quite true: an open door that goes directly into space instantly drains the room no matter how much air was in it before.
The other doors take time and so do hull breaches. Not sure how much time though.

I had to check it but yes, you are right. I think it should take some time but it should be faster than breaches.
kcd.Spektor
Posts: 586
Joined: Thu Nov 26, 2015 8:21 am

Re: Hurray! Another multiplayer clone project. :)

Postby kcd.Spektor » Mon Feb 08, 2016 9:15 am

Whiew :)
Think I've managed to replicate the oxygen mechanic.
I'm pretty sure that my algorithm is not the same as FTL has. (Because mine uses duct tape and some nails to work :mrgreen: )
But still I think it does the job.
Just need to balance it a bit, and during the next update I hope you will be satisfied by how it works. :D

But if anyone will be kind enough to draw his own oxygen algorithm - I would be very glad to try it out.

Don't ask how mine works (I'm too shamed to tell :? )
kcd.Spektor
Posts: 586
Joined: Thu Nov 26, 2015 8:21 am

Re: Hurray! Another multiplayer clone project. :)

Postby kcd.Spektor » Mon Feb 08, 2016 12:14 pm

Finally it's starting to look good :)
Image
jrb00001
Posts: 201
Joined: Fri Jan 15, 2016 2:22 pm

Re: Hurray! Another multiplayer clone project. :)

Postby jrb00001 » Mon Feb 08, 2016 4:30 pm

kcd.Spektor wrote:Whiew :)
Think I've managed to replicate the oxygen mechanic.
I'm pretty sure that my algorithm is not the same as FTL has. (Because mine uses duct tape and some nails to work :mrgreen: )
But still I think it does the job.
Just need to balance it a bit, and during the next update I hope you will be satisfied by how it works. :D

But if anyone will be kind enough to draw his own oxygen algorithm - I would be very glad to try it out.

Don't ask how mine works (I'm too shamed to tell :? )

You do not have to tell how yours works, just post the relevant source code :D
Mine would look something like this (pseudocode / not tested):

Code: Select all

OxygenCell[][] oxygen = new OxygenCell[cells.length][cells[0].length];
for (int x = 0; x < cells.length; x++) {
    for (int y = 0; y < cells[x].length; y++) {
        oxygen[x][y] = new OxygenCell();
    }
}

for (int x = 0; x < cells.length; x++) {
    for (int y = 0; y < cells[x].length; y++) {
        int walls = 0;
        double target = cells[x][y].oxygen;

        if (y > 0 && !cells[x][y].wallUp) {
            target += cells[x][y - 1].oxygen;
            walls++;
        }
        if (y < cells[x].length - 1 && !cells[x][y].wallDown) {
            target += cells[x][y + 1].oxygen;
            walls++;
        }
        if (x > 0 && !cells[x][y].wallLeft) {
            target += cells[x - 1][y].oxygen;
            walls++;
        }
        if (y < cells.length - 1 && !cells[x][y].wallRight) {
            target += cells[x + 1][y].oxygen;
            walls++;
        }

        target /= walls + 1;
        oxygen[x][y].diff = target - cells[x][y].oxygen;
    }
}

for (int x = 0; x < cells.length; x++) {
    for (int y = 0; y < cells[x].length; y++) {
        if (oxygen[x][y].diff != 0) {
            if (y > 0 && !cells[x][y].wallUp) {
                oxygen[x][y].up = oxygen[x][y - 1].diff;
            }
            if (y < cells[x].length - 1 && !cells[x][y].wallDown) {
                oxygen[x][y].down = oxygen[x][y + 1].diff;
            }
            if (x > 0 && !cells[x][y].wallLeft) {
                oxygen[x][y].left = oxygen[x - 1][y].diff;
            }
            if (y < cells.length - 1 && !cells[x][y].wallRight) {
                oxygen[x][y].right = oxygen[x + 1][y].diff;
            }

            double factor = oxygen[x][y].up + oxygen[x][y].down + oxygen[x][y].left + oxygen[x][y].right / -oxygen[x][y].diff;
            oxygen[x][y].up /= factor;
            oxygen[x][y].down /= factor;
            oxygen[x][y].left /= factor;
            oxygen[x][y].right /= factor;
        }
    }
}

for (int x = 0; x < cells.length - 1; x++) {
    for (int y = 0; y < cells[x].length - 1; y++) {
        if (y < cells.length - 1) {
            double move = Math.copySign(Math.min(Math.abs(oxygen[x][y].down), Math.abs(oxygen[x][y + 1].up)), oxygen[x][y].down) / DISTRIBUTION_RATE;
            cells[x][y].oxygen -= move;
            cells[x][y + 1].oxygen += move;
        }
    }
}


EDIT: Added the DISTRIBUTION_RATE constant. It should be less than 1 and says how fast the oxygen is distributed. Because open space has an oxygen level of 0, a DISTRIBUTION_RATE of 0.5d means that the oxygen level of an adjacent cell is halved each update.
Last edited by jrb00001 on Mon Feb 08, 2016 5:59 pm, edited 1 time in total.
User avatar
fdagpigj
Posts: 84
Joined: Sat Apr 25, 2015 3:14 pm

Re: Hurray! Another multiplayer clone project. :)

Postby fdagpigj » Mon Feb 08, 2016 5:27 pm

kcd.Spektor wrote:But if anyone will be kind enough to draw his own oxygen algorithm - I would be very glad to try it out.


I don't know Java but I made an algo in Python, no idea how good it is but whatever, I tested it and seems to work alright. Just modify the constants to what feels good in your conditions. This basically assumes oxygen inside a ship goes from 0.0 to 100.0

Code: Select all

def distributeOxygen(roomA, roomB):
   rate = 40.0
   voidOxygen = -500.0
   minChange = 0.1
   minOxygen = 0.0
   difference = abs(roomA.oxygen - roomB.oxygen)
   if roomA.oxygen < roomB.oxygen:
      sign = -1
   else:
      sign = 1
   change = min(max(difference / rate, minChange), difference/2.0)
   roomA.oxygen -= sign*change
   roomB.oxygen += sign*change
   if roomA.isVoid():
      roomA.oxygen = voidOxygen
   elif roomA.oxygen < minOxygen:
      roomA.oxygen = minOxygen
   if roomB.isVoid():
      roomB.oxygen = voidOxygen
   elif roomB.oxygen < minOxygen:
      roomB.oxygen = minOxygen

Edit: Made a few improvements to the function, including support for when A has less oxygen than B :oops:
kcd.Spektor
Posts: 586
Joined: Thu Nov 26, 2015 8:21 am

Re: Hurray! Another multiplayer clone project. :)

Postby kcd.Spektor » Mon Feb 08, 2016 6:10 pm

Don't forget that oxygen should also be replenished by all installed oxygen system.
And should be dispersed when there is no active O2 system :)

My main problem was caused by combination of 2 facts:
1. Oxygen is replenished every tick in each cell - by the amount gathered from all active O2 systems.
2. Every tick - adjacent room cells everage their o2 levels between them.

I was only able to solve this by changing the first fact - O2 is now replenished not every tick, but every 4th tick.
faggot
Posts: 1
Joined: Mon Feb 08, 2016 9:23 pm

Re: Hurray! Another multiplayer clone project. :)

Postby faggot » Mon Feb 08, 2016 9:30 pm

(ignore my username, my friend is a real jokester)


Hey: There are tons of people that would love to help, have you considered uploading your code to github?
jrb00001
Posts: 201
Joined: Fri Jan 15, 2016 2:22 pm

Re: Hurray! Another multiplayer clone project. :)

Postby jrb00001 » Mon Feb 08, 2016 9:45 pm

kcd.Spektor wrote:Don't forget that oxygen should also be replenished by all installed oxygen system.
And should be dispersed when there is no active O2 system :)

My main problem was caused by combination of 2 facts:
1. Oxygen is replenished every tick in each cell - by the amount gathered from all active O2 systems.
2. Every tick - adjacent room cells everage their o2 levels between them.

I was only able to solve this by changing the first fact - O2 is now replenished not every tick, but every 4th tick.

How much is replenished every tick? Is it more than the oxygen lost through the open doors?

I think the amount of replenished oxygen per cell should be divided by the amount of room cells. Bigger ships need more oxygen!
User avatar
fdagpigj
Posts: 84
Joined: Sat Apr 25, 2015 3:14 pm

Re: Hurray! Another multiplayer clone project. :)

Postby fdagpigj » Tue Feb 09, 2016 12:09 am

kcd.Spektor wrote:Don't forget that oxygen should also be replenished by all installed oxygen system.
And should be dispersed when there is no active O2 system :)

My main problem was caused by combination of 2 facts:
1. Oxygen is replenished every tick in each cell - by the amount gathered from all active O2 systems.
2. Every tick - adjacent room cells everage their o2 levels between them.

I was only able to solve this by changing the first fact - O2 is now replenished not every tick, but every 4th tick.


Well, I just made a simulation in pygame (python 2). No idea why. Because it's fun, I guess. I feel like I have something pretty close to the original now.

Code: Select all

import pygame
import math

pygame.init()

DISPLAY_WIDTH = 800
DISPLAY_HEIGHT = 600
FPS = 30

GRID_SIZE = 40
all_rooms = []
all_doors = []
GRID_WIDTH = 20
GRID_HEIGHT = 13
WALL_THICKNESS = 2


gameDisplay = pygame.display.set_mode((DISPLAY_WIDTH, DISPLAY_HEIGHT))
pygame.display.set_caption("Oxygen")

gameState = "playing"
oxygenPower = 1


class Door(object):
   def __init__(self, roomA, roomB=None, position=(0,0,0,1), startOpen=False):
      global all_doors
      super(Door, self).__init__()
      all_doors.append(self)
      self.roomA = roomA
      self.roomB = roomB
      self.position = position #(x, y, w, h) in relation to roomA x, y, dimensions
      self.open = startOpen

   def getRenderCoords(self):
      shape = (8, 30)
      if self.position[2] == 0: #vertical
         w, h = shape
         x = (self.roomA.x+self.position[0])*GRID_SIZE - w/2.0
         y = (self.roomA.y+self.position[1])*GRID_SIZE + (GRID_SIZE - h)/2.0
      else: #horizontal
         w, h = reversed(shape)
         x = (self.roomA.x+self.position[0])*GRID_SIZE + (GRID_SIZE - w)/2.0
         y = (self.roomA.y+self.position[1])*GRID_SIZE - h/2.0
      return (x, y, w, h)

   def getColour(self):
      if self.open:
         return (255, 200, 150)
      else:
         return (255, 100, 0)

   def tick(self):
      if self.open:
         if self.roomB != None:
            oxygenB = self.roomB.oxygen
         else:
            oxygenB = -500
         self.roomA.oxygen, oxygenB = distributeOxygen(self.roomA.oxygen, oxygenB)
         if self.roomB != None:
            self.roomB.oxygen = oxygenB



class Room(object):
   def __init__(self, x, y, dimensions=(1,1)):
      global all_rooms
      super(Room, self).__init__()
      all_rooms.append(self)
      self.x = x #coords on the grid, not screen
      self.y = y
      self.dimensions = dimensions
      self.oxygen = 100.0
      self.breaches = 0

   def getSize(self, dimension):
      return self.dimensions[dimension]

   def getColour(self):
      return (255,round(2.55*self.oxygen),round(2.55*self.oxygen))

   def getTileCount(self):
      return self.dimensions[0] * self.dimensions[1]
      
   def getBreachPos(self, n):
      return (n%self.getSize(0), n/self.getSize(1))

   def tick(self):
      self.oxygen -= 0.05
      for i in range(self.breaches):
         self.oxygen, _ = distributeOxygen(self.oxygen, 0.0)
      self.oxygen += (160 - self.oxygen) * oxygenPower / 700
      self.oxygen = max(min(self.oxygen, 100.0), 0.0)


def distributeOxygen(oxygenA, oxygenB):
   rate = 40.0
   minChange = 0.1
   minOxygen = 0.0
   difference = abs(oxygenA - oxygenB)
   if oxygenA < oxygenB:
      sign = -1
   else:
      sign = 1
   change = min(max(difference / rate, minChange), difference/2.0)
   oxygenA -= sign*change
   oxygenB += sign*change
   if oxygenA < minOxygen:
      oxygenA = minOxygen
   if oxygenB < minOxygen:
      oxygenB = minOxygen
   return oxygenA, oxygenB

def averageOxygen():
   totalOxygen = 0.0
   for room in all_rooms:
      totalOxygen += room.oxygen
   return totalOxygen / len(all_rooms)

def addBreach(x, y, amount):
   global all_rooms
   for i in xrange(len(all_rooms)):
      room = all_rooms[i]
      if x < room.x+room.getSize(0) and x >= room.x and y < room.y+room.getSize(1) and y >= room.y:
         all_rooms[i].breaches = max(min(all_rooms[i].breaches+amount, room.getTileCount()), 0)

def toggleDoor(x, y, newState=None):
   for door in all_doors:
      doorX, doorY, doorW, doorH = door.getRenderCoords()
      if x < doorX+doorW and x >= doorX and y < doorY+doorH and y >= doorY:
         if newState == None:
            door.open = not door.open
         else:
            door.open = newState
         return True
   return False

def toggleAllDoors(newState):
   if newState == True:
      openAirlocks = True
      for door in all_doors:
         if door.open == False and door.roomB != None:
            openAirlocks = False
            break
      for door in all_doors:
         if door.roomB != None or openAirlocks == True:
            door.open = True
   else:
      for door in all_doors:
         door.open = False

def canPlace(x, y, dimensions=(1,1)):
   w = dimensions[0]
   h = dimensions[1]
   if x+w > GRID_WIDTH or y+h > GRID_HEIGHT:
      print "Outside border"
      return False
   for room in all_rooms:
      if room.x+room.getSize(0) > x and x+w > room.x and room.y+room.getSize(1) > y and y+h > room.y:
         return False
   return True

def addOxygen(amount):
   global oxygenPower
   oxygenPower = max(oxygenPower + amount, 0)

def newGame():
   for room in ((4,4), (4,6), (4,8), (6,4), (6,6), (6,8)):
      new_room = Room(room[0],room[1], (2,2))
   Door(all_rooms[0])
   Door(all_rooms[1], all_rooms[0], (0,0,1,0))
   Door(all_rooms[1], all_rooms[0], (1,0,1,0))
   Door(all_rooms[2], all_rooms[1], (1,0,1,0))
   Door(all_rooms[3], all_rooms[0], (0,1,0,1))
   Door(all_rooms[3], all_rooms[0], (0,0,0,1))
   Door(all_rooms[4], all_rooms[3], (1,0,1,0))
   Door(all_rooms[4], all_rooms[1], (0,0,0,1))
   Door(all_rooms[5], all_rooms[2], (0,0,0,1))
   Door(all_rooms[5], all_rooms[4], (0,0,1,0))
   Door(all_rooms[5], all_rooms[4], (1,0,1,0))
   Door(all_rooms[4], position=(2,0,0,1))
   Door(all_rooms[4], position=(2,1,0,1))
   Door(all_rooms[5], position=(2,0,0,1))
   Door(all_rooms[3], position=(2,0,0,1))
   Door(all_rooms[2], position=(1,2,1,0))
   playGame()

def playGame():
   global gameState
   clock = pygame.time.Clock()
   iteration = 0
   while not gameState=="quitting":
      for event in pygame.event.get():
         if event.type == pygame.QUIT:
            gameState = "quitting"
         elif event.type == pygame.KEYDOWN:
            if event.key == pygame.K_z:
               toggleAllDoors(True)
            elif event.key == pygame.K_x:
               toggleAllDoors(False)
            elif event.key == pygame.K_a:
               addOxygen(-1)
            elif event.key == pygame.K_s:
               addOxygen(1)
            elif event.key == pygame.K_SPACE:
               if gameState == "playing":
                  gameState = "paused"
               else:
                  gameState = "playing"
         elif event.type == pygame.MOUSEBUTTONDOWN:
            x, y = event.pos
            if event.button == 3: #rightclick
               if x < GRID_SIZE*GRID_WIDTH and y < GRID_SIZE*GRID_HEIGHT:
                  x = x/GRID_SIZE
                  y = y/GRID_SIZE
                  if canPlace(x, y, (2,2)):
                     Room(x, y, (2,2))
                  else:
                     addBreach(x, y, 1)
            if event.button == 1: #leftclick
               if x < GRID_SIZE*GRID_WIDTH and y < GRID_SIZE*GRID_HEIGHT:
                  if not toggleDoor(x, y):
                     x = x/GRID_SIZE
                     y = y/GRID_SIZE
                     addBreach(x, y, -1)

      if gameState == "playing":
         for room in all_rooms:
            room.tick()
         for door in all_doors:
            door.tick()


      renderGame()
      clock.tick(FPS)

def renderGame():
   gameDisplay.fill((55,55,55))
   gameDisplay.fill((0,0,0), rect=[0, DISPLAY_HEIGHT-80, DISPLAY_WIDTH, 80])
   for room in all_rooms:
      gameDisplay.fill((0,0,0), rect=[room.x*GRID_SIZE, room.y*GRID_SIZE, room.getSize(0)*GRID_SIZE, room.getSize(1)*GRID_SIZE])
      gameDisplay.fill(room.getColour(), rect=[room.x*GRID_SIZE+WALL_THICKNESS, room.y*GRID_SIZE+WALL_THICKNESS, room.getSize(0)*GRID_SIZE-2*WALL_THICKNESS, room.getSize(1)*GRID_SIZE-2*WALL_THICKNESS])
      for breach in range(room.breaches):
         (breach_x, breach_y) = room.getBreachPos(breach)
         gameDisplay.fill((25,35,55), rect=[(room.x+0.25+breach_x)*GRID_SIZE, (room.y+0.25+breach_y)*GRID_SIZE, 0.5*GRID_SIZE, 0.5*GRID_SIZE])
   for door in all_doors:
      gameDisplay.fill(door.getColour(), rect=door.getRenderCoords())
   text(DISPLAY_WIDTH/2, DISPLAY_HEIGHT-40, "Oxygen Power: %i   Average Oxygen: %f"%(oxygenPower,averageOxygen()), colour=(0,255,0))
   if gameState == "paused":
      text(DISPLAY_WIDTH/2, DISPLAY_HEIGHT-60, "PAUSED", colour=(255,255,255))

   pygame.display.update()

def text(x, y, text, colour=(255,255,255), size=25, w=0, h=0):
   textSurface = get_msg(text, colour, size)
   textRect = textSurface.get_rect()
   textRect.center = x, y+h/4
   gameDisplay.blit(textSurface, textRect)

cache = {}
def get_msg(msg, colour, size):
   if not size in cache:
      cache[size] = {}
   if not colour in cache[size]:
      cache[size][colour] = {}
   if not msg in cache[size][colour]:
      fontobj = pygame.font.Font("arial.ttf", size)
      cache[size][colour][msg] = fontobj.render(msg, False , colour)
   return cache[size][colour][msg]

newGame()

pygame.quit()
quit()


Quick edit: Removed some unused bits of code from the program
kcd.Spektor
Posts: 586
Joined: Thu Nov 26, 2015 8:21 am

Re: Hurray! Another multiplayer clone project. :)

Postby kcd.Spektor » Tue Feb 09, 2016 5:28 am

faggot wrote:Hey: There are tons of people that would love to help, have you considered uploading your code to github?

It's not open source. ;)
Plus I can handle the coding part,
I need help with art and sound.


jrb00001 wrote:How much is replenished every tick? Is it more than the oxygen lost through the open doors?

The oxygen in the airlock is set to 0 every tick when the airlock is open.
Then every tick air of nearby cells is averaged between them, so the air is sucked out pretty fast :)

I think the amount of replenished oxygen per cell should be divided by the amount of room cells. Bigger ships need more oxygen!

This can lead to 0 oxygen replenishment on really big ships.
And on smaller ships it would be replenished really slow, but venting it out will still be fast, and that would be a disballance.

fdagpigj wrote:Well, I just made a simulation in pygame (python 2). No idea why. Because it's fun, I guess. I feel like I have something pretty close to the original now.


Well I'm not familiar with python, so that code doesn't help any bit :)
Can you draw a logic tree for your algorithm?