Your browser does not support the canvas tag.

previous        Show / Hide Source        Download        next
/*
Freespace Live Map
By: Khan-ali Ibrahim for IMC @ Sheridan College
October 2016 - Still

Explore a randomly generated map that is alive and busy! Trade between space stations to affect their 
growth yourself. The space stations grow and level up themselves, and get more resources.

The goal was to make something that will be active and change on its own, I've been surprised when I leave
the program running and come back to it being completly changed.

- move towards the edge of the map to scroll around
- click on a space station to select it
- use the menu on the side to give and take resources from the selected station
 */

//Varibles
int numOfStations = 85;
//Focus for UI
int UItargetFocus = 0;
float mapSize = 0.5, 
  mapScrollSpeed = 3;
//world center for the map scrolling
PVector worldCenter;

KXIcore core;
spaceStation[] stations = new spaceStation[numOfStations];
tradeUI ui = new tradeUI(new PVector(700, 300));


void setup() {
  size(800, 600);
  //frame rate for counting logic
  frameRate(60);
  
  //send out instructions
  println("Welcome to Freespace! move towards the edge of the map to scroll around, click on a space station to select it, use the menu on the side to give and take resources from the selected station");

  //world center to center of frame
  worldCenter = new PVector(width/2, height/2);

  for (int i = 0; i < numOfStations; i++) {
    //gaussian  is much prettier than plain random
    stations[i] = new spaceStation(
      (randomGaussian() * width * mapSize), 
      (randomGaussian() * height * mapSize), i);
  }
}

void draw() {
  //initilize core
  core = new KXIcore(mouseX, mouseY, mousePressed);

  background(#263238);
  
  //handle map movement
  handleMapMove(50);

  //draw the trade lines, under the stations
  for (int i = 0; i < stations.length; i++) {
    stations[i].drawTradeLine(worldCenter);

    //if the station is clicked, put this here to handle logic before drawing
    if (core.mouseClickCenter(stations[i].position.x + worldCenter.x, stations[i].position.y  + worldCenter.y, stations[i].size.x, stations[i].size.y)) {
      UItargetFocus = i;
      //println("Hello from " + stations[i].id);
    }
  }

  //draw stations and activate the station trading logic
  for (int i = 0; i < stations.length; i++) {
    stations[i].findTradePartner(stations);
    stations[i].display(worldCenter);
  }

  //tell the trade handling what to focus on
  ui.updateTarget(UItargetFocus, stations);
  ui.display();
}

//handles the movement around the map
//everything is drawn relitive to the world center, if it moves EVERYTHING moves
void handleMapMove (int area) {
  //if on edge of screen and not to the limits, move the world center
  if (mouseX < area && worldCenter.x < 900) {
    worldCenter.x += mapScrollSpeed;
  } else if (mouseX > (width - 250) && mouseX < (width - 200) && worldCenter.x > -200) {
    worldCenter.x -= mapScrollSpeed;
  }

  if (mouseY < area && worldCenter.y < 700) {
    worldCenter.y += mapScrollSpeed;
  } else if (mouseY > (height - 50) && worldCenter.y > -200) {
    worldCenter.y -= mapScrollSpeed;
  }
}
/*
  KXI core functions V1 - FOR PUBLIC
 by: Khan-ali Ibrahim

 Functions for common use tasks, work smart not hard people.
 Mainly to handle mouse events but I stuff other stuff I need in there.

 This is a class I pass around, you are free to use as long as you credit me.

 Don't instantiate/call the function in SETUP, do it in DRAW or else the mouse events won't work
 repeat do (name of object) = new KXIcore(mouseX, mouseY, mousePressed); IN DRAW
 */

class KXIcore {
  //varibles
  float mX, //mouseX
    mY; //mouseY
  boolean mP; //mousePressed

  //Constructor to pass the mouse inputs along
  KXIcore(int x, int y, boolean p) {
    mX = x;
    mY = y;
    mP = p;
  }

  //function that draws an invisible rectangle in CORNERS mode
  //if the mouse is over the invisble box it returns true
  boolean mouseOverCorner(float leftX, float leftY, float rightX, float rightY) {
    if (mX > leftX && mX < rightX && mY > leftY && mY < rightY) {
      return true;
    } else {
      return false;
    }
  }

  //function that draws an invisible rectangle in CORNERS mode
  //if the mouse is over and pressed in the invisble box it returns true
  boolean mouseClickCorner(float leftX, float leftY, float rightX, float rightY) {
    if (mouseOverCorner(leftX, leftY, rightX, rightY) && mP) {
      return true;
    } else {
      return false;
    }
  }

  //function that draws an invisible rectangle in CENTER mode
  //if the mouse is over the invisble box it returns true
  boolean mouseOverCenter(float centerX, float centerY, float sizeX, float sizeY) {
    //finds the sides of the rectangle
    float leftSide = centerX - (sizeX/2),
      rightSide = centerX + (sizeX/2),
      topSide = centerY - (sizeY/2),
      bottomSide = centerY + (sizeY/2);

    if (mX > leftSide && mX < rightSide && mY > topSide && mY < bottomSide) {
      return true;
    } else {
      return false;
    }
  }

  //function that draws an invisible rectangle in CENTER mode
  //if the mouse is over and pressed in the invisble box it returns true
  boolean mouseClickCenter(float centerX, float centerY, float sizeX, float sizeY) {
    if (mouseOverCenter(centerX, centerY, sizeX, sizeY) && mP) {
      return true;
    } else {
      return false;
    }
  }

  //function that when given a two points and one point inbetween them will calculate the percentage
  //between the two points // I used this for my sliders
  float findPercent(int min, int max, int value) {
    //I don't remember the math since I wrote this when I was at night and tired, but it works
    float rangeDistance = max - min;
    float valueDistance = value - min;

    //returns percentage
    return (valueDistance / rangeDistance) * 1;
  }


  //finds the centerpoint between two points
  float findMidpoint (float pointA, float pointB) {
    //middle school math people, middle school math
    return (pointA + pointB) / 2;
  }
}
/*
[X] My code works and I don't know why 
[ ] My code doesn't work and I don't know why
*/
class spaceStation {
  //varibles start//
  boolean active = true;

  PVector position, 
    size = new PVector (10, 10), 
    tradePartner;
  int id, 
    xp = 1, 
    xplvlup = 250, 
    lvl = 1;

  float productionCoolDown = 1, 
    productionCoolDownMax = 60, //1 sec
    tradeCoolDown = 2, 
    tradeCoolDownMax = 120; //2 sec

  boolean canTrade = false, 
    canProduce = false;
  boolean [] wantsTrade = new boolean[] {false, false, false, false};

  int tradeDistance = 150, 
    tradeAmmount = 100;

  int [] resources = new int[4], 
    resourcesProduction = new int[4], 
    resourcesConsume = new int[4];
  int resourcesMax = 1000;
  //varibles end - holy fuck thats a lot//

  //space station initilization
  spaceStation (float tempx, float tempy, int num) {
    //pass through the information
    position = new PVector(tempx, tempy);
    id = num;
    tradePartner = position;

    for (int i = 0; i < resources.length; i++) {
      //random resource production
      resources[i] = int(random(resourcesMax * 0.2, resourcesMax));
      resourcesProduction[i] = int(random(10, 150));
      //consumption is the same
      resourcesConsume[i] = 40;
    }

    //sets the cooldowns
    productionCoolDown = productionCoolDownMax;
    tradeCoolDown = tradeCoolDownMax;
  }

  //put this in draw
  void display(PVector worldCenter) {
    //handle cooldowns
    handleCooldowns();

    //handle resources
    handleResources();

    //drawing
    //drawTradeLine(); //do this in main code
    drawStation(worldCenter);
  }

  //draws the station
  void drawStation(PVector cen) {
    color col = 128;
    boolean red = false, grn = false, blu = false;

    //figures out what colour to put based on the resources remaining
    for (int i = 0; i < resources.length; i++) {
      if (resources[i] < (resourcesMax * 0.25)) {
        red = true;
      } else if (resources[i] > (resourcesMax * 0.75)) {
        blu = true;
      } else if (resources[i] > (resourcesMax * 0.4)) {
        grn = true;
      }
    }

    if (red) {
      col = color(255, 0, 0);
    } else if (blu) {
      col = color(0, 255, 0);
    } else if (grn) {
      col = color(0, 0, 255);
    }

    strokeWeight(beam);
    stroke(255);
    fill(col);
    ellipseMode(CENTER);
    ellipse(cen.x + position.x, cen.y + position.y, size.x, size.y);
  }

  //varibles for the beam pulses
  float beam = 0.5, 
    beamSronk = 0;

  //draws the trade line
  void drawTradeLine (PVector cen) {
    //sets to pulse once
    if (beam > 0.5) {
      beam = sin(beamSronk / 16) + 1;
      beamSronk += 1;
    } else {
      beamSronk = 0;
    }

    strokeWeight(beam);
    stroke(255);
    //draws line between this and the trade partner
    line(cen.x + position.x, cen.y + position.y, 
      cen.x + tradePartner.x, cen.y + tradePartner.y);
  }

  //generates and manages station resrouces
  void handleResources() {
    if (canProduce) {
      for (int i = 0; i < resources.length; i++) {
        //if not filled
        if (resources[i] < resourcesMax) {
          resources[i] += resourcesProduction[i];
        }
        
        //if not too low
        if (resources[i] > resourcesConsume[i]) { 
          resources[i] -= resourcesConsume[i];
        } 
        
        //if we have enough and fine to trade it off
        if (resources[i] > (resourcesMax * 0.4)) {
          wantsTrade[i] = true;
        } else {
          wantsTrade[i] = false;
        }
      }

      //handle xp here for balence
      handleXP();

      //resets the trade cooldown
      canProduce = false;
      tradeCoolDown = tradeCoolDownMax;
    }

    //make sure resources are within limits
    for (int i = 0; i < resources.length; i++) {
      if (resources[i] >= resourcesMax) {
        resources[i] = resourcesMax;
      } else if (resources[i] < 0) {
        resources[i] = 0;
      }
    }
  }

  //finds a trading partner //this was stupid to write
  void findTradePartner(spaceStation[] target) {
    //cycle through all resourcesions
    for (int i = 0; i < target.length; i++) {
      //if it finds itself then knock it off otherwise if its close enough and can trade will trade
      if (dist(position.x, position.y, target[i].position.x, target[i].position.y) == 0) {
        break;
      } else if (dist(position.x, position.y, target[i].position.x, target[i].position.y) < tradeDistance && canTrade) {
        //loops through the resources
        for (int h = 0; h < resources.length; h++) {
          //if finds a resourcesion with a surplus of resources, trades
          if (wantsTrade[h] == false && target[i].wantsTrade[h] == true) {
            traderesources(h, target, i);
            tradePartner = target[i].position;
          }
        }
      }
    }
  }

  //handles trade
  void traderesources(int type, spaceStation[] target, int num) {
    // if it can trade
    if (canTrade) {
      //trades
      resources[type] += tradeAmmount;
      target[num].resources[type] -= tradeAmmount;

      //reset the productionCoolDown
      productionCoolDown = productionCoolDownMax;
      canTrade = false;
      beam = 0.6;

      //bonus xp for doing a trade
      xp += 2;
      target[num].xp += 1;
    }
  }

  void handleCooldowns() {
    //handle production Cooldown
    if (productionCoolDown < 1) {
      canTrade = true;
    } else {
      canTrade = false;
      productionCoolDown -= 1;
    }

    //handle trade cooldown
    if (tradeCoolDown < 1) {
      canProduce = true;
    } else {
      canProduce = false;
      tradeCoolDown -= 1;
    }
  }

  //sorts out the xp
  void handleXP() {
    //loops through the resources
    for (int i = 0; i < resources.length; i++) {
      //if the resources is in surplus get xp, if its too low lose xp
      if (resources[i] > (resourcesMax * 0.75)) {
        xp += 2;
      } else if (resources[i] < resourcesConsume[i]) {
        xp -= 4;
      } else if (resources[i] < (resourcesMax * 0.25)) {
        xp -= 1;
      } else {
        xp += 1;
      }
    }

    //if you lose too much xp level down
    if (xp < -(xplvlup / 4)) {
      handleLVLUP(false);
    } 

    //if ready to levelup
    if (xp > xplvlup) {
      handleLVLUP(true);
    }
  }

  //handles levels up or downs, 1 is up and 0 is down
  void handleLVLUP (boolean up) {
    //level cap is 8
    if (up && lvl != 8) {
      xp = 0;
      lvl++;
    } else if (lvl != 1) {
      xp = 0;
      lvl--;
    } else {
      xp = 0;
    }

    //stats change based on level
    resourcesMax = resourcesMax < 2500 ? int(1000 * (lvl * 0.5)) : 3000;
    xplvlup = int(250 * (lvl * 0.5));

    //size changes based on level
    size.x = 8 + lvl;
    size.y = 8 + lvl;

    //changes the production on levelup
    for (int i = 0; i < resources.length; i++) {
      resourcesProduction[i] = int(random(10, 150 + (lvl * 20)));
      resourcesConsume[i] = int(40 + (lvl * 10));
    }
  }
}
/*
  This can bully space stations FYI
*/

class tradeUI {
  PVector position;
  PVector targetPosition;
  int targetID = -1, 
    targetLvl, 
    resourceMax;
  int[] resource = new int[4];

  boolean[] hasResource = {false, false, false, false};

  int barLength = 41, 
    barHeight = 250, 
    barPadding = 7;

  //cooldown to prevent spamming
  float tradeCd = 30;
  
  color[] resourceCol = new color[] {#FF5722, #8BC34A, #00BCD4, #673AB7};

  tradeUI (PVector pos) {
    position = pos;
  }

  //updates information that the UI knows
  void updateTarget(int id, spaceStation[] tar) {
    targetID = id;
    targetLvl = tar[id].lvl;
    targetPosition = tar[id].position;

    for (int i = 0; i < resource.length; i++) {
      resource[i] = tar[id].resources[i];
    }

    resourceMax = tar[id].resourcesMax;
  }

  void display() {
    //setup
    rectMode(CENTER);
    barHeight = int(250 * (float(resourceMax) / 3000));

    //indicator
    noFill();
    stroke(255);
    strokeWeight(2);
    ellipseMode(CENTER);
    ellipse(targetPosition.x + worldCenter.x, targetPosition.y + worldCenter.y, 25, 25);

    //background
    fill(#90A4AE);
    noStroke();
    rect(position.x, position.y, 200, height);

    //station name
    fill(0);
    textSize(12);
    text("lvl: " + targetLvl + " | Station Number # " + targetID, position.x - 90, 30);

    //bars and stuff
    for (int i = 0; i < resource.length; i++) {
      noStroke();
      rectMode(CORNERS);

      //bar fill
      fill(resourceCol[i]);
      rect(position.x - 100 + (barLength * i) + (barPadding * (i + 1)), position.y - 190, 
        position.x - 100 + (barLength * (i + 1)) + (barPadding * (i + 1)), barHeight + position.y - 190);

      //bar background
      fill(#607D8B);
      rect(position.x - 100 + (barLength * i) + (barPadding * (i + 1)), position.y - 190, 
        position.x - 100 + (barLength * (i + 1)) + (barPadding * (i + 1)), 
        int(-(core.findPercent(0, resourceMax, resource[i]) - 1) * barHeight) + position.y - 190);

      //button
      rectMode(CENTER);
      fill(#37474F);
      rect(position.x - 79 + (barLength * i) + (barPadding * (i + 1)), position.y - 210, barLength, barLength * 0.6);

      //button arrow
      drawArrow(int(position.x - 79 + (barLength * i) + (barPadding * (i + 1))), int(position.y - 210), hasResource[i]);

      //button logic
      if (core.mouseClickCenter(position.x - 79 + (barLength * i) + (barPadding * (i + 1)), position.y - 210, barLength, barLength * 0.6)
        && tradeCd < 0) {
        handleTrade(i);
        tradeCd = 30;
      }

      //invintory
      noStroke();
      fill(hasResource[i] ? resourceCol[i] : #607D8B);
      rect(position.x - 79 + (barLength * i) + (barPadding * (i + 1)), position.y - 240, barLength, barLength * 0.6);

      //fill(0); //debug
      //text("Resource " + (i + 1) + ": " + resource[i] + " / " +  resourceMax, position.x - 80, (100 + (i * 30)));
    }

    //handleCooldown
    tradeCd -= 1;
  }

  //UI trading
  void handleTrade(int num) {
    if (hasResource[num]) {
      stations[targetID].resources[num] += 200;
      hasResource[num] = false;
    } else {
      stations[targetID].resources[num] -= 200;
      hasResource[num] = true;
    }
  }

  //flips it up and down
  void drawArrow(int x, int y, boolean up) {
    stroke(255);
    strokeWeight(3);
    //messed up with where it was, too lazy to move around the code
    if (!up) {
      line(x, y - 5, x - 15, y + 5);
      line(x, y - 5, x + 15, y + 5);
    } else {
      line(x, y + 5, x - 15, y - 5);
      line(x, y + 5, x + 15, y - 5);
    }
  }
}