/* built with Studio Sketchpad:
* https://sketchpad.cc
*
* observe the evolution of this sketch:
* https://studio.sketchpad.cc/sp/pad/view/ro.KbFetYSpHGL/rev.38348
*
* authors:
* Richard Wen
*
*
* license (unless otherwise specified):
* creative commons attribution-share alike 3.0 license.
* https://creativecommons.org/licenses/by-sa/3.0/
*/
// The goal is to produce a top-down survival game
// Please don't copy this; I've been working on it for a long time
// This sketch was created by Richard Wen
/*
Notes for myself:
- Manually defined angles will be in radians
- Use the constand PI to define the angles
- Angles received from atan() functions will already be in radians, so there is no need to convert them
- The debug UI can be opened by setting the debug state to true
- To test functions like in an interpreter, set the first parameter of testing() to true
- Set the second parameter to true if the program should stop after testing
- To create a new item:
- Define a sprite [PImage stickImg]
- Load the sprite [loadImage(".../stick.png")]
- Insert the item in findImage [imageOut = stickImg]
- Put the item in any specialized locations (recipes/other uses)
- To create a new structure:
- Create a new Building subclass [class Tent extends Building]
- Call the super()
- Define buildingWidth, buildingHeight, and shape
- Define buildTime (amount of time spent in construction)
- Define buildCost [buildCost = new BuildCost({}, {})]
- Create a new ArrayList to hold the structure [ArrayList<Building> tents = new ArrayList<Building>()]
- Put the stucture in the getBuildingArray lookup [return tents]
- Define a new structure instance in player.constuction.place() [tents.add(new Tent())]
- player.build.checkBuildClick()
- Build the structure if it needs building
- Use the structure if you can
- Exit the structure if it is being used
- Display the structure in draw() [tents.get(i).show()]
*/
/* @pjs preload="/static/uploaded_resources/p.27803/null.png"; */
PImage nullImg;
PImage backpackImg;
PImage hammerImg;
PImage gloveOpenImg;
PImage gloveClosedImg;
PImage stickImg;
PImage rocksImg;
PImage symbolToolLeft;
PImage symbolToolRight;
PImage symbolHead;
PImage symbolHandLeft;
PImage symbolHandRight;
PImage symbolChest;
PImage symbolFootLeft;
PImage symbolFootRight;
PImage symbolLegs;
void loadImages() {
nullImg = loadImage("/static/uploaded_resources/p.27803/null.png");
backpackImg = loadImage("/static/uploaded_resources/p.27803/backpack.png");
hammerImg = loadImage("/static/uploaded_resources/p.27803/hammer.png");
gloveOpenImg = loadImage("/static/uploaded_resources/p.27803/gloveOpen.png");
gloveClosedImg = loadImage("/static/uploaded_resources/p.27803/gloveClosed.png");
stickImg = loadImage("/static/uploaded_resources/p.27803/stick.png");
rocksImg = loadImage("/static/uploaded_resources/p.27803/rocks.png");
}
void testing(boolean isActive, boolean stopProgram) {
if (isActive) {
// Insert everything to be tested here
trees.add(new Tree(100, 100, 0));
// Everything to be tested ends here
if (stopProgram){
exit();
}
}
}
float debugFloat1 = 0;
float debugFloat2 = 0;
float debugFloat3 = 0;
String debugString1 = "";
String debugString2 = "";
boolean debugMenu = true; // Set true to enable debug UI; Set false to disable debug UI
int debugState = 0; // The active page of the debug menu (is 0 when the menu is hidden)
void debug(boolean isActive) {
if (isActive) {
fill(0);
textFont(defaultFont);
textAlign(LEFT, BOTTOM);
switch (debugState) {
case 0:
break;
case 1:
text("Positional Debug:", 50, 40); // Increase x by 300, increase y by 20
text("Player Location: " + str(player.x) + ", " + str(player.y), 50, 60);
text("Player in Cam: " + str(player.xInCam) + ", " + str(player.yInCam), 50, 80);
text("Camera Location: " + str(width / 2 + player.xInCam - player.x) + ", " + str(height / 2 + player.yInCam - player.y), 50, 100);
text(str(wDown), 350, 60);
text(str(aDown), 300, 80);
text(str(sDown), 350, 80);
text(str(dDown), 400, 80);
text(str(ground.tileX) + ", " + str(ground.tileY), 350, 100);
text(str(mouseX), 300, 120);
text(str(mouseY), 350, 120);
break;
case 2:
text("Technical Debug:", 50, 40);
text("gameTime = " + str(gameTime), 50, 60);
text("isActive = " + str(actionMenu.isActive), 50, 80);
text("subMenu = " + actionMenu.subMenu, 50, 100);
text("Energy: " + str(player.energy), 50, 120);
text("Health: " + str(player.health), 50, 140);
text("restClock = " + str(player.restClock), 50, 160);
text("replenishing = " + str(player.replenishing), 50, 180);
text(str(debugFloat1), 350, 60);
text(str(debugFloat2), 350, 80);
text(str(debugFloat3), 350, 100);
text(debugString1, 350, 120);
text(debugString2, 350, 140);
break;
case 3:
text("Structure Debug:", 50, 40);
text(player.construction.building, 50, 60);
text("isBuilding = " + str(player.build.isBuilding), 50, 80);
text("buildingType = " + player.build.buildingType, 50, 100);
text("buildingKey = " + str(player.build.buildingKey), 50, 120);
text("isUsing = " + str(player.build.isUsing), 250, 80);
text("usingType = " + player.build.usingType, 250, 100);
text("usingKey = " + str(player.build.usingKey), 250, 120);
text("itemLifted = " + str(actionMenu.inventoryMaster.itemLifted), 50, 160);
text("liftedItem = " + actionMenu.inventoryMaster.liftedItem, 50, 180);
text("liftedCount = " + str(actionMenu.inventoryMaster.liftedCount), 50, 200);
break;
default:
debugState = 0;
break;
}
}
}
PImage findImage(String name) {
PImage imageOut;
switch (name) {
case "Sticks":
imageOut = stickImg;
break;
case "Rocks":
imageOut = rocksImg;
break;
default:
imageOut = nullImg;
break;
}
return imageOut;
}
int preventDiv0(float f) { // When there is a div by 0 error possible, add preventDiv0(f) to the denominator, where f is the numerator
if (f == 0) {
return 100; // This function only works if the denominator can not be the negative of this value
// Currently: 0 <= possible denominators
}
return 0;
}
// Make a function that passes an integer and returns an array of buildings
int buildingTypeCount = 2; // The number of valid building types
ArrayList<Building> getBuildingArray(String buildingArray) {
switch (buildingArray) {
case "0":
case "Tent":
return tents;
case "1":
case "Campfire":
return campfires;
default:
return new ArrayList<Building>();
}
}
void drawCursor() {
if (actionMenu.isActive) {
if (actionMenu.inventoryMaster.itemLifted || mousePressed) {
noCursor();
imageMode(CENTER);
image(gloveClosedImg, mouseX, mouseY + 2, 30, 30);
}
else {
noCursor();
imageMode(CENTER);
image(gloveOpenImg, mouseX, mouseY, -30, 30);
}
}
else {
for (int i = 0; i < buildingTypeCount; i++) {
for (int j = 0; j < getBuildingArray(str(i)).size(); j++) {
if (getBuildingArray(str(i)).get(j).hovered() && getBuildingArray(str(i)).get(j).buildProgress < getBuildingArray(str(i)).get(j).buildTime) {
noCursor();
imageMode(CENTER);
image(hammerImg, mouseX, mouseY, 40, 40);
return;
}
}
}
cursor(CROSS);
}
}
class Ground {
// Tiles
int tileWidth = 50;
int tileHeight = 50;
int tileX = 0;
int tileY = 0;
void show(float centerX, float centerY) {
rectMode(CORNER);
for (tileX = int((centerX - width / 2 - tileWidth) / tileWidth); tileX <= (centerX + width / 2) / tileWidth; tileX++) {
for (tileY = int((centerY - height / 2 - tileHeight) / tileHeight); tileY <= (centerY + height / 2) / tileHeight; tileY++) {
if (abs(tileX + tileY) % 2 == 0) {
fill(255);
}
else {
fill(100);
}
rect(tileX * tileWidth, tileY * tileHeight, tileWidth, tileHeight);
if (debugMenu) {
fill(0);
textAlign(CENTER, CENTER);
textFont(defaultFont);
if (debugState == 1) {
text(str(tileX) + ", " + str(tileY), tileX * tileWidth + tileWidth / 2, tileY * tileHeight + tileHeight / 2);
}
else if (debugState == 2) {
text(str(tileX + tileY), tileX * tileWidth + tileWidth / 2, tileY * tileHeight + tileHeight / 2);
}
}
}
}
}
}
Ground ground;
// PLAYER
class Player {
class Movement { // Controls the player's movement
boolean isEnabled = true;
float playerMovementX = 0; // 1 if moving right, 0 if no x movement, -1 if moving left
float playerMovementY = 0;
float playerVelX = 0; // Function of playerMovementX and the angle of motion
float playerVelY = 0;
float playerSpeed = 100;
float xInCamLimit = 0.25; // Value between 0 and 1 representing the fraction of the screen width available for player movement relative to camera center
float yInCamLimit = 0.25;
void shift(float xIn, float yIn) { // Move the player and calculate camera movement
x += xIn;
y += yIn;
xInCam += xIn;
yInCam += yIn;
if (abs(xInCam) > 0.5 * xInCamLimit * width) {
xInCam *= 0.5 * xInCamLimit * width / abs(xInCam);
}
if (abs(yInCam) > 0.5 * yInCamLimit * height) {
yInCam *= 0.5 * yInCamLimit * height / abs(yInCam);
}
dir = atan(yIn / xIn);
if (xIn < 0) {
dir += PI;
}
}
void walk() { // Calculate movement for one frame
playerMovementX = 0;
if (aDown) {
playerMovementX--;
}
if (dDown) {
playerMovementX++;
}
playerMovementY = 0;
if (wDown) {
playerMovementY--;
}
if (sDown) {
playerMovementY++;
}
if (shiftDown && !replenishing) { // Running
playerSpeed = 160;
}
else {
playerSpeed = 100;
if (energy < 20) {
replenishing = true;
}
}
playerVelX = playerSpeed * playerMovementX / (pow(pow(playerMovementX, 2) + pow(playerMovementY, 2), 0.5) + preventDiv0(playerMovementX));
playerVelY = playerSpeed * playerMovementY / (pow(pow(playerMovementX, 2) + pow(playerMovementY, 2), 0.5) + preventDiv0(playerMovementY));
if (playerVelX != 0 || playerVelY != 0) {
shift(playerVelX / frameRate, playerVelY / frameRate);
}
}
}
Movement movement = new Movement();
class Construction { // Controls placement of a building
String building = "null"; // Name of the building being placed (null if no building is being placed)
float placeX;
float placeY;
float placeR;
float placeAngle;
float placementRange = 100; // The max range that the building can be placed at
void calcPlacement() { // Calculate the placement location of the building based on the mouse position
placeX = mouseX - (xInCam + width / 2);
placeY = mouseY - (yInCam + height / 2);
placeR = pow(pow(placeX, 2) + pow(placeY, 2), 0.5); // The distance between the mouse and the player
if (placeR > placementRange) { // Check if the mouse is out of range of the player
placeX *= placementRange / placeR;
placeY *= placementRange / placeR;
}
placeAngle = atan(placeY / placeX) + radians(90);
if (placeX < 0) {
placeAngle += PI;
}
}
void place() {
calcPlacement();
switch (building) {
case "Tent":
tents.add(new Tent(x + placeX, y + placeY, placeAngle));
break;
case "Campfire":
campfires.add(new Campfire(x + placeX, y + placeY, placeAngle));
break;
default:
break;
}
building = "null";
}
void show() {
calcPlacement();
pushMatrix();
translate(x + placeX, y + placeY);
rotate(placeAngle); // Rotate the building to face the player (the -90 is because the orientation of the building should be perpendicular to the line between it and the player)
scale(-abs(placeX) / placeX, 1); // Flip the image if atan(placeY / placeX) was in the 1st or 2nd quadrant (this is because of the symmetrical properties of arctangents)
fill(0, 0, 0, 75);
noStroke();
rectMode(CENTER);
switch (building) {
case "null":
break;
case "Tent":
rect(0, 0, 100, 80);
break;
case "Campfire":
ellipse(0, 0, 60, 60);
break;
default:
break;
}
stroke(0);
popMatrix();
}
}
Construction construction = new Construction();
class Build { // Controls using and building a structure
boolean isUsing = false;
String usingType = "null";
int usingKey = 0;
boolean isBuilding = false;
String buildingType = "null";
int buildingKey = 0;
float buildingRange = 100;
float xOfBuilding; // Just to tell the draw() function where to place the range circle
float yOfBuilding;
boolean inBuildingRange = false;
Build() {
}
void checkBuildClick() { // Called when the mouse is pressed; Checks if the player clicked on a structure; Initiates using or building a structure
if (!isUsing) {
for (int i = 0; i < tents.size(); i++) {
if (tents.get(i).hovered()) { // If the player clicked on the structure
if (tents.get(i).buildProgress < tents.get(i).buildTime) { // If the structure needs building
isBuilding = true;
buildingType = "Tent";
buildingKey = i;
return;
}
else { // If the structure can be used
if (pow(pow(tents.get(i).x - x, 2) + pow(tents.get(i).y - y, 2), 0.5) <= buildingRange) {
isUsing = true;
usingType = "Tent";
usingKey = i;
movement.shift(tents.get(usingKey).x - x, tents.get(usingKey).y - y);
movement.isEnabled = false;
isVisible = false;
return;
}
}
}
}
for (int i = 0; i < campfires.size(); i++) {
if (campfires.get(i).hovered()) { // If the player clicked on the structure
if (campfires.get(i).buildProgress < campfires.get(i).buildTime) { // If the structure needs building
isBuilding = true;
buildingType = "Campfire";
buildingKey = i;
return;
}
else { // If the structure can be used
// campfires.get(i).use();
}
}
}
}
else {
isUsing = false;
switch (usingType) {
case "Tent":
movement.isEnabled = true;
isVisible = true;
movement.shift(tents.get(usingKey).x + (tents.get(usingKey).buildingWidth / 2 + 30) * cos(tents.get(usingKey).angle) - x, tents.get(usingKey).y + (tents.get(usingKey).buildingWidth / 2 + 30) * sin(tents.get(usingKey).angle) - y);
break;
default:
break;
}
}
}
void checkBuildHold() { // Called every frame; Checks if the player is currently building a structure
xOfBuilding = getBuildingArray(buildingType).get(buildingKey).x;
yOfBuilding = getBuildingArray(buildingType).get(buildingKey).y;
buildingRange = 100; // At this point, all buildings have the same building range; If this changes, add a switch statement here that checks BuildingType
if (getBuildingArray(buildingType).get(buildingKey).buildProgress < getBuildingArray(buildingType).get(buildingKey).buildTime) { // If the structure still needs building
if (pow(pow(getBuildingArray(buildingType).get(buildingKey).x - x, 2) + pow(getBuildingArray(buildingType).get(buildingKey).y - y, 2), 0.5) <= buildingRange) { // If the structure is within range
inBuildingRange = true;
if (getBuildingArray(buildingType).get(buildingKey).hovered()) {
getBuildingArray(buildingType).get(buildingKey).buildProgress += 1 / frameRate; // Make building progress
}
}
}
else {
getBuildingArray(buildingType).get(buildingKey).buildProgress = getBuildingArray(buildingType).get(buildingKey).buildTime;
isBuilding = false;
}
}
}
Build build = new Build();
class Harvest { // Controls harvesting naturals
boolean isHarvesting = false;
String naturalType = "null";
int naturalKey = 0;
float harvestingRange = 100;
float xOfNatural;
float yOfNatural;
boolean inHarvestingRange = false;
void checkHarvestClick() {
}
}
float x;
float y;
float dir; // Angle of player's viewing direction from the +x axis
float xInCam; // x distance from center of camera
float yInCam; // y distance from center of camera
boolean isVisible = true;
Player() {
x = 0;
y = 0;
dir = 0;
xInCam = 0;
yInCam = 0;
}
float energy = 100;
float health = 100;
boolean replenishing = false; // When true, energy-using actions are no longer possible; becomes set to false when reaching sufficient energy
float restClock = 0; // Counts how long the player has been resting (the longer the player has been resting, the more energy they gain per tick)
void tick() {
if (movement.isEnabled) {
movement.walk();
}
energy += 3 / frameRate;
health += 0.75 / frameRate;
if (build.isUsing) {
switch (build.usingType) {
case "Tent":
health += 2.5 / frameRate;
energy += 6 / frameRate;
break;
default:
break;
}
}
if (!(movement.playerMovementX == 0 && movement.playerMovementY == 0) && player.movement.isEnabled) { // If walking
energy -= 2.5 / frameRate;
energy += (1.5 - 15 / (restClock + 10)) / frameRate; // After 5 seconds, energy regen increased by 0.5 en/sec; approach +1.5 en/sec; total +2 en/sec
restClock += 0.75 / frameRate;
if (shiftDown && !replenishing) { // If running
restClock = 0;
energy -= 5.5 / frameRate;
}
}
else {
energy += (3 - 12 / (restClock + 4)) / frameRate; // After 2 seconds, energy regen increased by 1 en/sec; approach +3 en/sec; total +6 en/sec
restClock += 1 / frameRate;
if (energy < 20) {
replenishing = true;
}
}
if (replenishing) {
energy -= 0.5 / frameRate; // Decreased energy regen if fatigued (under 20% energy)
}
if (energy < 0) {
energy = 0;
replenishing = true;
}
else if (energy > 100) {
energy = 100;
}
else if (energy > 20) {
replenishing = false;
}
if (health < 0) {
health = 0;
// death
}
else if (health > 100) {
health = 100;
}
}
void show() {
if (isVisible) {
pushMatrix();
translate(x, y);
rotate(dir);
fill(0);
ellipse(0, 0, 40, 40);
fill(255, 0, 0);
ellipse(20, 0, 5, 5);
popMatrix();
}
}
void showStatusBars() {
rectMode(CORNER);
fill(215, 225, 225);
rect(75, 35, 200, 20);
rect(75, 55, 200, 10);
fill(220, 30, 100);
rect(75, 35, 2 * health, 20); // Health bar
fill(70, 230, 240);
rect(75, 55, 2 * energy, 10); // Energy bar
fill(70, 180, 240);
if (energy > 20) { // Emergency energy bar
rect(75, 55, 40, 10);
}
else {
rect(75, 55, 2 * energy, 10);
}
fill(215, 200, 190);
ellipse(50, 50, 60, 60);
}
}
Player player;
// BUILDINGS
class Building { // Superclass for all structures
float buildingWidth;
float buildingHeight;
float x;
float y;
float angle;
float sinAngle;
float cosAngle;
String shape; // rect, ellipse
float buildProgress = 0; // How long the player has been building the structure
float buildTime; // How long the player needs to build the structure for it to be complete
String[] buildCostItems;
int[] buildCostCounts;
boolean inUse = false;
Building(float xIn, float yIn, float angleIn) { // Do not construct a building; Construct the subtype
x = xIn;
y = yIn;
angle = angleIn;
sinAngle = sin(angle);
cosAngle = cos(angle);
}
boolean hovered() {
float xToMouse = camOriginX + mouseX - x;
float yToMouse = camOriginY + mouseY - y;
float u = xToMouse * cosAngle + yToMouse * sinAngle;
float v = yToMouse * cosAngle - xToMouse * sinAngle;
switch (shape) {
case "rect":
if (abs(u) <= buildingWidth / 2 && abs(v) <= buildingHeight / 2) {
return true;
}
else {
return false;
}
case "ellipse":
if (pow(pow(u / (buildingWidth / 2), 2) + pow(v / (buildingHeight / 2), 2), 0.5) <= 1) {
return true;
}
else {
return false;
}
default:
return false;
}
}
void appearance() {}; // By default has nothing; Redefine in the subclass
void show() {
pushMatrix();
translate(x, y);
rotate(angle);
appearance();
popMatrix();
if (buildProgress < buildTime) {
rectMode(CORNER);
fill(240, 240, 240, 80);
rect(x - 40, y - 10, 80, 20);
rectMode(CORNER);
fill(210, 255, 255);
rect(x - 40, y - 10, 80 * buildProgress / buildTime, 20);
}
/* fill(240, 240, 240, 80);
ellipse(x, y, 50, 50);
fill(210, 255, 255);
arc(x, y, 50, 50, radians(-90), radians(-90) + radians(360) * buildProgress / buildTime, PIE); */ // Unfortunately, Studio Sketchpad does not support the arc() function
if (debugMenu && debugState == 3) {
fill(0);
textAlign(CENTER, CENTER);
text(str(int(buildProgress * 100) / 100) + "/" + str(buildTime), x, y);
}
}
}
class Tent extends Building {
Tent(float xIn, float yIn, float angleIn) {
super(xIn, yIn, angleIn);
buildingWidth = 100;
buildingHeight = 80;
shape = "rect";
buildTime = 12;
String[] buildCostItems = {"Sticks", "Rocks", "leather", "rope"};
int[] buildCostCounts = {6, 4, 3, 2};
}
void appearance() {
if (player.build.isUsing && player.build.usingType == "Tent" && player.build.usingKey == tents.indexOf(this)) {
inUse = true;
}
else {
inUse = false;
}
noStroke();
if (inUse) {
fill(255, 200 + 15 * sin(gameTime / 100), 0, 5);
for (int i = 2; i < buildingHeight; i += 2) {
ellipse(buildingWidth / 2, 0, i, i);
}
}
else {
fill(0, 0, 0, 80);
triangle(buildingWidth / 2, buildingHeight / 2, buildingWidth / 2, -buildingHeight / 2, buildingWidth / 2 - 2, 0);
}
stroke(0);
fill(80, 120, 70);
if (buildProgress >= buildTime) {
quad(-buildingWidth / 2, buildingHeight / 2, buildingWidth / 2, buildingHeight / 2, buildingWidth / 2 - 2, 0, -buildingWidth / 2 - 2, 0);
quad(-buildingWidth / 2, -buildingHeight / 2, buildingWidth / 2, -buildingHeight / 2, buildingWidth / 2 - 2, 0, -buildingWidth / 2 - 2, 0);
noStroke();
fill(0, 0, 0, 80);
triangle(buildingWidth / 2 - 2, 0, buildingWidth / 2 - 22, buildingHeight / 2 - 12, buildingWidth / 2, buildingHeight / 2);
triangle(buildingWidth / 2 - 2, 0, buildingWidth / 2 - 22, -buildingHeight / 2 + 12, buildingWidth / 2, -buildingHeight / 2);
stroke(0);
fill(80, 120, 70);
triangle(buildingWidth / 2 - 2, 0, buildingWidth / 2 - 20, buildingHeight / 2 - 10, buildingWidth / 2, buildingHeight / 2);
triangle(buildingWidth / 2 - 2, 0, buildingWidth / 2 - 20, -buildingHeight / 2 + 10, buildingWidth / 2, -buildingHeight / 2);
}
else {
quad(-buildingWidth / 2, buildingHeight / 2, buildingWidth / 2, buildingHeight / 2, buildingWidth / 2 + 2, 0, -buildingWidth / 2 - 2, 0);
quad(-buildingWidth / 2, -buildingHeight / 2, buildingWidth / 2, -buildingHeight / 2, buildingWidth / 2 + 2, 0, -buildingWidth / 2 - 2, 0);
}
}
}
ArrayList<Building> tents = new ArrayList<Building>();
class Campfire extends Building {
class Flame {
float x;
float y;
color c;
float size;
float maxSize;
float time;
float maxTime;
Flame() {
regen();
}
void regen() {
x = random(-buildingWidth / 5, buildingWidth / 5);
y = random(-buildingHeight / 5, buildingHeight / 5);
maxSize = random(buildingWidth / 6, buildingWidth / 2.5);
maxTime = random(0.5, 2);
time = 0;
size = 0;
c = color(random(230, 255), random(100, 220), random(10, 50));
}
void tick() {
time += 1 / frameRate;
size = maxSize - 2 * maxSize / maxTime * abs(time - maxTime / 2);
if (size < 0) {
regen();
}
}
void show() {
fill(c);
ellipse(x, y, size, size);
}
}
Flame[] flames = new Flame[12];
Campfire(float xIn, float yIn, float angleIn) {
super(xIn, yIn, angleIn);
buildingWidth = 60;
buildingHeight = 60;
shape = "ellipse";
buildTime = 8;
String[] buildCostItems = {"Sticks", "Rocks"};
int[] buildCostCounts = {8, 12};
for (int i = 0; i < flames.length; i++) {
flames[i] = new Flame();
}
}
void appearance() {
pushMatrix();
rotate(radians(10));
fill(190, 190, 200);
for (int i = 0; i < 18; i++) {
ellipse(0, buildingWidth / 2 - 4, 8, 8);
rotate(radians(20));
}
popMatrix();
pushMatrix();
rectMode(CENTER);
fill(110, 80, 50);
for (int i = 0; i < 3; i++) {
rect(0, 0, buildingWidth + 10, buildingWidth / 6);
rotate(radians(120));
}
popMatrix();
if (buildProgress >= buildTime) {
noStroke();
fill(255, 180, 0, 4);
for (int i = 2; i < 2 * buildingWidth; i += 2) {
ellipse(0, 0, i, i);
}
for (int i = 0; i < flames.length; i++) {
flames[i].tick();
flames[i].show();
}
stroke(0);
}
}
}
ArrayList<Building> campfires = new ArrayList<Building>();
// ENVIRONMENT
int naturalTypeCount = 1;
ArrayList<Natural> getNaturalArray(String naturalArray) {
}
class Natural { // Superclass for all natural structures
float naturalWidth;
float naturalHeight;
float x;
float y;
float angle;
float sinAngle;
float cosAngle;
String shape; // rect, ellipse
float harvestProgress = 0; // How long the player has been building the structure
float harvestTime; // How long the player needs to build the structure for it to be complete
boolean isHarvestable = true;
Natural(float xIn, float yIn, float angleIn) { // Do not construct a building; Construct the subtype
x = xIn;
y = yIn;
angle = angleIn;
sinAngle = sin(angle);
cosAngle = cos(angle);
}
boolean hovered() {
float xToMouse = camOriginX + mouseX - x;
float yToMouse = camOriginY + mouseY - y;
float u = xToMouse * cosAngle + yToMouse * sinAngle;
float v = yToMouse * cosAngle - xToMouse * sinAngle;
switch (shape) {
case "rect":
if (abs(u) <= naturalWidth / 2 && abs(v) <= naturalHeight / 2) {
return true;
}
else {
return false;
}
case "ellipse":
if (pow(pow(u / (naturalWidth / 2), 2) + pow(v / (naturalHeight / 2), 2), 0.5) <= 1) {
return true;
}
else {
return false;
}
default:
return false;
}
}
void appearance() {}; // By default has nothing; Redefine in the subclass
void show() {
pushMatrix();
translate(x, y);
rotate(angle);
appearance();
popMatrix();
if (harvestProgress > 0) {
rectMode(CORNER);
fill(240, 240, 240, 80);
rect(x - 40, y - 10, 80, 20);
rectMode(CORNER);
fill(210, 255, 255);
rect(x - 40, y - 10, 80 * harvestProgress / harvestTime, 20);
}
/* fill(240, 240, 240, 80);
ellipse(x, y, 50, 50);
fill(210, 255, 255);
arc(x, y, 50, 50, radians(-90), radians(-90) + radians(360) * harvestProgress / harvestTime, PIE); */ // Unfortunately, Studio Sketchpad does not support the arc() function
if (debugMenu && debugState == 3) {
fill(0);
textAlign(CENTER, CENTER);
text(str(int(harvestProgress * 100) / 100) + "/" + str(harvestTime), x, y);
}
}
}
class Tree extends Natural {
Tree(float xIn, float yIn, float angleIn) {
super(xIn, yIn, angleIn);
naturalWidth = 80;
naturalHeight = 80;
shape = "ellipse";
harvestTime = 1;
}
void appearance() {
fill(100, 255, 100);
ellipse(0, 0, naturalWidth, naturalHeight);
}
}
ArrayList<Natural> trees = new ArrayList<Natural>();
// ENTITIES
class DroppedItem {
float x;
float y;
String itemName;
int itemCount;
DroppedItem(float xIn, float yIn, String itemNameIn, int itemCountIn) {
x = xIn;
y = yIn;
itemName = itemNameIn;
itemCount = itemCountIn;
timeOff = random(2 * PI);
}
boolean collecting = false; // Set to true when the player steps over the item
float collectionClock = 0;
float collectionTime = 0.15; // The total collection time
float startX;
float startY;
void collectTick() { // Activate if the item is being collected
if (collecting) {
collectionClock += 1 / frameRate;
if (collectionClock > collectionTime) {
if (actionMenu.inventoryMaster.canCollect(itemName)) {
actionMenu.inventoryMaster.collectItem(itemName, itemCount);
droppedItems.remove(droppedItems.indexOf(this));
}
}
else {
x = startX + (player.x - startX) * pow(collectionClock / collectionTime, 2);
y = startY + (player.y - startY) * pow(collectionClock / collectionTime, 2);
}
}
else if (pow(pow(player.x - x, 2) + pow(player.y - y, 2), 0.5) < 40) { // The collection distance is alterable
collecting = true;
collectionClock = 0;
startX = x;
startY = y;
}
}
float timeOff; // Used to create a hovering effect
void show() {
imageMode(CENTER);
image(findImage(itemName), x, y, 40 * (1 - collectionClock / collectionTime), 40 * (1 - collectionClock / collectionTime));
// image(findImage(itemName), x + 2 * cos(gameTime / 50 + timeOff), y + 4 * sin(gameTime / 50 + timeOff), 40, 40); // Creates a hovering effect, but is slower
if (debugMenu && debugState == 2) {
textFont(defaultFont);
textAlign(CENTER, CENTER);
fill(0);
text(itemName, x, y - 6);
text(str(itemCount), x, y + 6);
}
}
}
ArrayList<DroppedItem> droppedItems = new ArrayList<DroppedItem>();
// UI
class Button {
PFont buttonFont;
float x;
float y;
float buttonWidth;
float buttonHeight;
String buttonText;
boolean buttonPressed = false;
Button(float xIn, float yIn, float buttonWidthIn, float buttonHeightIn, String buttonTextIn) {
x = xIn;
y = yIn;
buttonWidth = buttonWidthIn;
buttonHeight = buttonHeightIn;
buttonText = buttonTextIn;
buttonFont = createFont("Georgia", buttonHeight * 0.75);
textFont(buttonFont);
if (textWidth(buttonText) > buttonWidth * 0.9) {
buttonFont = createFont("Georgia", buttonHeight * 0.75 * buttonWidth / textWidth(buttonText) * 0.9);
}
}
boolean hovered() { // Return true if the cursor is on the button; return false otherwise
if (mouseX > x && mouseX < x + buttonWidth && mouseY > y && mouseY < y + buttonHeight) {
return true;
}
else {
return false;
}
}
void show() {
if (buttonPressed) {
fill(175);
}
else {
fill(220);
}
rectMode(CORNER);
rect(x, y, buttonWidth, buttonHeight);
fill(0);
textFont(buttonFont);
textAlign(CENTER, CENTER);
text(buttonText, x + buttonWidth / 2, y + buttonHeight / 2 - 3);
}
}
class ItemSlot {
PFont slotFont;
float x;
float y;
float slotWidth;
float slotHeight;
String itemName = "null";
int itemCount = 0;
ItemSlot(float xIn, float yIn, float slotWidthIn, float slotHeightIn) {
x = xIn;
y = yIn;
slotWidth = slotWidthIn;
slotHeight = slotHeightIn;
slotFont = createFont("Georgia", 15);
}
boolean hovered() { // Return true if the cursor is on the slot; return false otherwise
if (mouseX > x && mouseX < x + slotWidth && mouseY > y && mouseY < y + slotHeight) {
return true;
}
else {
return false;
}
}
void show() {
rectMode(CORNER);
fill(175);
rect(x, y, slotWidth, slotHeight);
if (hovered()) {
fill(230, 230, 230, 95);
rect(x, y, slotWidth, slotHeight);
}
float drawImageX = x + slotWidth / 2;
float drawImageY = y + slotHeight / 2;
if (itemCount != 0) {
imageMode(CENTER);
image(findImage(itemName), drawImageX, drawImageY - 2, slotWidth, slotHeight);
textFont(slotFont);
textAlign(RIGHT, BOTTOM);
fill(0);
text(str(itemCount), drawImageX + slotWidth / 2, drawImageY + slotHeight / 2);
}
if (debugMenu && debugState == 2) {
textFont(defaultFont);
textAlign(CENTER, CENTER);
fill(0);
text(itemName, drawImageX, drawImageY - 6);
text(str(itemCount), drawImageX, drawImageY + 6);
}
}
}
class ActionMenu {
boolean isActive = false;
String subMenu = "items"; // items, craft, build,
ItemSlot[] inventory = new ItemSlot[32];
ItemSlot[] belt = new ItemSlot[4];
ItemSlot[] equipment = new ItemSlot[9];
class ButtonMaster { // Used to manage and navigate buttons in the submenus
Button itemsTab = new Button(80, 300, 95, 40, "Items");
Button craftTab = new Button(195, 300, 95, 40, "Craft");
Button buildTab = new Button(310, 300, 95, 40, "Build");
Button equipTab = new Button(425, 300, 95, 40, "Equip");
Button[] itemsButtons = {
};
Button[] craftButtons = {
};
Button[] buildButtons = {
new Button(80, 60, 100, 40, "Tent"),
new Button(310, 60, 100, 40, "Campfire")
};
Button[] equipButtons = {
};
ButtonMaster() {
}
void checkClicksPress() { // See if the player clicked on any of the buttons
if (itemsTab.hovered()) {
subMenu = "items";
}
else if (craftTab.hovered()) {
subMenu = "craft";
}
else if (buildTab.hovered()) {
subMenu = "build";
}
else if (equipTab.hovered()) {
subMenu = "equip";
}
else if (inventoryMaster.itemLifted) {
switch (subMenu) {
case "items":
for (int i = 0; i < inventory.length; i++) {
if (inventory[i].hovered()) {
inventoryMaster.placeItem(inventoryMaster.itemSlotArray("inventory"), i);
break;
}
}
break;
case "craft":
break;
case "build":
break;
case "equip":
for (int i = 0; i < equipment.length; i++) {
if (equipment[i].hovered()) {
inventoryMaster.placeItem(inventoryMaster.itemSlotArray("equipment"), i);
break;
}
}
for (int i = 0; i < belt.length; i++) {
if (belt[i].hovered()) {
inventoryMaster.placeItem(inventoryMaster.itemSlotArray("belt"), i);
break;
}
}
break;
default:
break;
}
}
else {
switch (subMenu) {
case "items":
for (int i = 0; i < inventory.length; i++) { // Check if an item slot with an item in it was clicked
if (inventory[i].hovered()) {
inventoryMaster.liftItem(inventoryMaster.itemSlotArray("inventory"), i);
break;
}
}
break;
case "craft":
break;
case "build":
for (int i = 0; i < buildButtons.length; i++) { // Check if a button was clicked
if (buildButtons[i].hovered()) {
buildButtons[i].buttonPressed = true;
}
}
break;
case "equip":
for (int i = 0; i < equipment.length; i++) { // Check if an item slot with an item in it was clicked
if (equipment[i].hovered()) {
inventoryMaster.liftItem(inventoryMaster.itemSlotArray("equipment"), i);
break;
}
}
for (int i = 0; i < belt.length; i++) { // Check if an item slot with an item in it was clicked
if (belt[i].hovered()) {
inventoryMaster.liftItem(inventoryMaster.itemSlotArray("belt"), i);
break;
}
}
break;
default:
break;
}
}
}
void checkClicksRelease() {
switch (subMenu) {
case "items":
break;
case "craft":
break;
case "build":
for (int i = 0; i < buildButtons.length; i++) {
if (buildButtons[i].hovered() && buildButtons[i].buttonPressed) {
player.construction.building = buildButtons[i].buttonText;
actionMenu.isActive = false;
break;
}
}
break;
case "equip":
break;
default:
break;
}
}
void unclick() {
switch (subMenu) {
case "items":
for (int i = 0; i < itemsButtons.length; i++) {
itemsButtons[i].buttonPressed = false;
}
break;
case "craft":
for (int i = 0; i < craftButtons.length; i++) {
craftButtons[i].buttonPressed = false;
}
break;
case "build":
for (int i = 0; i < buildButtons.length; i++) {
buildButtons[i].buttonPressed = false;
}
break;
case "equip":
for (int i = 0; i < equipButtons.length; i++) {
equipButtons[i].buttonPressed = false;
}
break;
default:
break;
}
}
void show() {
itemsTab.buttonPressed = false; // Unclick all of the tabs, then later reclick the active tab
craftTab.buttonPressed = false;
buildTab.buttonPressed = false;
equipTab.buttonPressed = false;
switch (subMenu) {
case "items":
for (int i = 0; i < itemsButtons.length; i++) {
itemsButtons[i].show();
}
for (int i = 0; i < inventory.length; i++) {
inventory[i].show();
}
itemsTab.buttonPressed = true;
break;
case "craft":
for (int i = 0; i < craftButtons.length; i++) {
craftButtons[i].show();
}
craftTab.buttonPressed = true;
break;
case "build":
for (int i = 0; i < buildButtons.length; i++) {
buildButtons[i].show();
}
buildTab.buttonPressed = true;
break;
case "equip":
for (int i = 0; i < equipButtons.length; i++) {
equipButtons[i].show();
}
for (int i = 0; i < equipment.length; i++) {
equipment[i].show();
if (equipment[i].itemCount == 0) {
switch (i) { // Draw the equipment symbol
case 0:
break;
default:
break;
}
}
}
for (int i = 0; i < belt.length; i++) {
belt[i].show();
}
equipTab.buttonPressed = true;
break;
default:
break;
}
itemsTab.show();
craftTab.show();
buildTab.show();
equipTab.show();
if (inventoryMaster.itemLifted) { // Show the item being moved
imageMode(CENTER);
image(findImage(inventoryMaster.liftedItem), mouseX, mouseY, inventory[0].slotWidth, inventory[0].slotHeight);
textAlign(RIGHT, BOTTOM);
textFont(inventory[0].slotFont);
fill(0);
text(str(inventoryMaster.liftedCount), mouseX + inventory[0].slotWidth / 2, mouseY + inventory[0].slotHeight / 2);
}
}
}
ButtonMaster buttonMaster;
class InventoryMaster {
String[] itemSlotArrays = {"inventory", "equipment", "belt"};
boolean itemLifted = false; // Set to true when the player clicks an item in their inventory, moving it
String liftedItem = "null"; // Array of the lifted item
int liftedCount = 0; // Key of the lifted item
InventoryMaster() {
inventory[0].itemName = "Sticks";
inventory[0].itemCount = 3;
inventory[1].itemName = "Rocks";
inventory[1].itemCount = 3;
debugString1 = inventory[0].itemName;
debugFloat1 = inventory[0].itemCount;
}
ItemSlot[] itemSlotArray(String arrayName) {
switch (arrayName) {
case "inventory":
return inventory;
case "equipment":
return equipment;
case "belt":
return belt;
default:
return new ItemSlot[0];
}
}
void liftItem(ItemSlot[] fromArray, int fromKey) {
if (fromArray[fromKey].itemCount == 0) {
return;
}
if (mouseButton == LEFT) {
itemLifted = true;
liftedItem = fromArray[fromKey].itemName;
liftedCount = fromArray[fromKey].itemCount;
fromArray[fromKey].itemName = "null";
fromArray[fromKey].itemCount = 0;
}
else if (mouseButton == RIGHT) {
int originalCount = fromArray[fromKey].itemCount;
itemLifted = true;
fromArray[fromKey].itemCount = int(fromArray[fromKey].itemCount / 2);
liftedItem = fromArray[fromKey].itemName;
liftedCount = originalCount - fromArray[fromKey].itemCount;
if (fromArray[fromKey].itemCount == 0) {
fromArray[fromKey].itemName = "null";
}
}
}
void placeItem(ItemSlot[] toArray, int toKey) {
if (mouseButton == LEFT) {
if (toArray[toKey].itemName == liftedItem || toArray[toKey].itemCount == 0) {
toArray[toKey].itemName = liftedItem;
toArray[toKey].itemCount += liftedCount;
itemLifted = false;
}
else {
String movingItem = liftedItem;
int movingCount = liftedCount;
liftedItem = toArray[toKey].itemName;
liftedCount = toArray[toKey].itemCount;
toArray[toKey].itemName = movingItem;
toArray[toKey].itemCount = movingCount;
}
}
else if (mouseButton == RIGHT) {
if (toArray[toKey].itemName == liftedItem || toArray[toKey].itemCount == 0) {
toArray[toKey].itemName = liftedItem;
liftedCount--;
toArray[toKey].itemCount++;
if (liftedCount == 0) {
itemLifted = false;
}
}
}
}
void dropItem(int dropCount) {
liftedCount -= dropCount;
droppedItems.add(new DroppedItem(player.x + 50 * cos(player.dir) + random(-5, 5), player.y + 50 * sin(player.dir) + random(-5, 5), liftedItem, dropCount));
if (liftedCount == 0) {
itemLifted = false;
}
}
boolean canCollect(String collectingItem) {
for (int i = 0; i < inventory.length; i++) {
if (inventory[i].itemCount == 0 || inventory[i].itemName == collectingItem) {
return true;
}
}
return false;
}
void collectItem(String collectingName, int collectingCount) {
for (int i = 0; i < inventory.length; i++) {
if (inventory[i].itemName == collectingName) {
inventory[i].itemCount += collectingCount;
return;
}
}
for (int i = 0; i < inventory.length; i++) {
if (inventory[i].itemCount == 0) {
inventory[i].itemName = collectingName;
inventory[i].itemCount = collectingCount;
return;
}
}
}
}
InventoryMaster inventoryMaster;
ActionMenu() {
for (int i = 0; i < inventory.length; i++) {
inventory[i] = new ItemSlot(57.5 * (i % 8) + 80, 60 * int(i / 8) + 60, 37.5, 37.5);
}
for (int i = 0; i < belt.length; i++) {
belt[i] = new ItemSlot(57.5 * i + 80, 240, 37.5, 37.5);
}
for (int i = 0; i < equipment.length; i++) {
equipment[i] = new ItemSlot(57.5 * (i % 3) + 108.75, 60 * int(i / 3) + 60, 37.5, 37.5);
}
buttonMaster = new ButtonMaster();
inventoryMaster = new InventoryMaster();
}
void show() {
fill(0, 0, 0, 60);
rectMode(CORNER);
rect(0, 0, width, height);
fill(240);
rectMode(CENTER);
rect(width / 2, height / 2, width * 0.8, height * 0.8);
buttonMaster.show();
}
}
ActionMenu actionMenu;
PFont defaultFont;
void setup() {
size(600, 400);
frameRate(60);
cursor(CROSS);
defaultFont = createFont("Lucida Sans", 12);
loadImages();
ground = new Ground();
player = new Player();
actionMenu = new ActionMenu();
testing(true, false);
}
int gameTime = 0; // Begins at 0, increases by 1 every frame
float camOriginX = 0;
float camOriginY = 0;
void draw() {
gameTime++;
background(200);
// CALCULATIONS
player.tick();
camOriginX = player.x - width / 2 - player.xInCam; // For top-left corner of camera
camOriginY = player.y - height / 2 - player.yInCam;
if (player.build.isBuilding) {
player.build.checkBuildHold();
}
for (int i = droppedItems.size() - 1; i >= 0; i--) {
droppedItems.get(i).collectTick();
}
// GRAPHICS
pushMatrix(); // Move the origin opposite to the player so the player stays in frame
translate(-camOriginX, -camOriginY);
ground.show(camOriginX + width / 2, camOriginY + height / 2); // Create a tile pattern to help see movement
if (player.build.isBuilding) {
fill(0, 0, 0, 30);
ellipse(player.build.xOfBuilding, player.build.yOfBuilding, 2 * player.build.buildingRange, 2 * player.build.buildingRange);
}
for (int i = 0; i < tents.size(); i++) {
tents.get(i).show();
}
for (int i = 0; i < campfires.size(); i++) {
campfires.get(i).show();
}
player.show(); // Draw the player
player.construction.show(); // Draw the building box
for (int i = 0; i < trees.size(); i++) {
trees.get(i).show();
}
for (int i = 0; i < droppedItems.size(); i++) {
droppedItems.get(i).show();
}
popMatrix(); // Return the origin to (0, 0) to prepare for the next frame
player.showStatusBars();
if (actionMenu.isActive) {
actionMenu.show();
}
imageMode(CORNER);
image(backpackImg, width - 80, height - 95, 75, 90); // Draw inventory icon
drawCursor();
debug(debugMenu);
}
boolean wDown = false; // For keys that must be held down while other keys are being pressed
boolean aDown = false;
boolean sDown = false;
boolean dDown = false;
boolean shiftDown = false;
void keyPressed() {
if (key == 'w' || key == 'W') {
wDown = true;
}
if (key == 'a' || key == 'A') {
aDown = true;
}
if (key == 's' || key == 'S') {
sDown = true;
}
if (key == 'd' || key == 'D') {
dDown = true;
}
if (keyCode == SHIFT) {
shiftDown = true;
}
if (key == 'e' || key == 'E') {
actionMenu.isActive = !actionMenu.isActive;
player.construction.building = "null";
if (actionMenu.inventoryMaster.itemLifted) {
actionMenu.inventoryMaster.dropItem(actionMenu.inventoryMaster.liftedCount);
}
}
if ((key == 'l' || key == 'L') && debugMenu) {
debugState++;
}
if (key == ESC) {
key = 0;
if (actionMenu.isActive) {
actionMenu.isActive = false;
}
else if (player.construction.building != "null") {
player.construction.building = "null";
}
else {
// pause
}
}
}
void keyReleased() {
if (key == 'w' || key == 'W') {
wDown = false;
}
if (key == 'a' || key == 'A') {
aDown = false;
}
if (key == 's' || key == 'S') {
sDown = false;
}
if (key == 'd' || key == 'D') {
dDown = false;
}
if (keyCode == SHIFT) {
shiftDown = false;
}
}
void mousePressed() {
if ((mouseX > width - 68 && mouseX < width - 18 && mouseY > height - 88 && mouseY < height - 8) || (mouseX > width - 77 && mouseX < width - 9 && mouseY > height - 42 && mouseY < height - 14)) {
actionMenu.isActive = !actionMenu.isActive; // Check if the player clicked on the backpack icon
player.construction.building = "null";
if (actionMenu.inventoryMaster.itemLifted) {
actionMenu.inventoryMaster.dropItem(actionMenu.inventoryMaster.liftedCount);
}
}
else if (!actionMenu.isActive) {
if (player.construction.building != "null") {
if (mouseButton == LEFT) {
player.construction.place();
}
else if (mouseButton == RIGHT) {
player.construction.building = "null";
}
}
else {
if (mouseButton == LEFT) {
player.build.checkBuildClick();
player.harvest.checkHarvestClick();
}
}
}
else {
if ((mouseX < width * 0.1 || mouseX > width * 0.9) || (mouseY < height * 0.1 || mouseY > height * 0.9)) {
if (actionMenu.inventoryMaster.itemLifted) {
if (mouseButton == LEFT) {
actionMenu.inventoryMaster.dropItem(actionMenu.inventoryMaster.liftedCount);
}
else if (mouseButton == RIGHT) {
actionMenu.inventoryMaster.dropItem(1);
}
}
else {
actionMenu.isActive = false;
}
}
else {
actionMenu.buttonMaster.checkClicksPress();
}
}
}
void mouseReleased() {
if (mouseButton == LEFT) {
player.build.isBuilding = false;
if (actionMenu.isActive) {
actionMenu.buttonMaster.checkClicksRelease();
actionMenu.buttonMaster.unclick();
}
}
}