/*
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);
}
}
}