Character customization, fixed progress bar, updated number formatting and time formatting

This commit is contained in:
2023-02-04 13:37:20 +02:00
parent 2a38098d1c
commit 4a625f83a3
26 changed files with 353 additions and 33 deletions

View File

@@ -59,13 +59,17 @@ export default class Plant {
public getTimeToGrow(): number {
if (!this.plant) return 0;
return this.plant.timeToGrow;
return this.plant.timeToGrow * Math.pow(0.9, Math.floor(this.level / 25));
}
getPrice() {
if (!this.plant) return 0;
return this.formulasService.getPrice(this.plant.basePrice, this.plant.growthRate, this.level);
}
getPriceFormatted() {
return this.utilitiesService.formatNumber(this.getPrice());
}
buy() {
if (this.playerService.seeds >= this.getPrice()) {
this.playerService.seeds -= this.getPrice();
@@ -85,9 +89,16 @@ export default class Plant {
return this.plant.reward;
}
getRewardFormatted() {
return this.utilitiesService.formatNumber(this.getReward());
}
getRewardTotal() {
return this.level * this.getReward();
}
getRewardTotalFormatted() {
return this.utilitiesService.formatNumber(this.getRewardTotal());
}
progress(): number {
return this.timePassed / this.getTimeToGrow() * 100;

View File

@@ -0,0 +1,16 @@
import { TestBed } from '@angular/core/testing';
import { ConfigService } from './config.service';
describe('ConfigService', () => {
let service: ConfigService;
beforeEach(() => {
TestBed.configureTestingModule({});
service = TestBed.inject(ConfigService);
});
it('should be created', () => {
expect(service).toBeTruthy();
});
});

69
src/app/config.service.ts Normal file
View File

@@ -0,0 +1,69 @@
import { Injectable } from '@angular/core';
@Injectable({
providedIn: 'root'
})
export class ConfigService {
private hats = [
'',
'assets/hat.png',
];
private glasses = [
'',
'assets/glasses.png',
];
private hoodies = [
'',
'assets/hoodie.png',
];
private pants = [
'',
'assets/shorts.png',
];
private shoes = [
'',
'assets/shoes.png',
];
constructor() { }
public getHats(): string[] {
return this.hats;
}
public getHatByIndex(index: number): string {
return this.hats[index] ?? '';
}
public getGlasses(): string[] {
return this.glasses;
}
public getGlassesByIndex(index: number): string {
return this.glasses[index] ?? '';
}
public getHoodies(): string[] {
return this.hoodies;
}
public getHoodieByIndex(index: number): string {
return this.hoodies[index] ?? '';
}
public getPants(): string[] {
return this.pants;
}
public getPantsByIndex(index: number): string {
return this.pants[index] ?? '';
}
public getShoes(): string[] {
return this.shoes;
}
public getShoesByIndex(index: number): string {
return this.shoes[index] ?? '';
}
}

View File

@@ -3,7 +3,7 @@
<img src="/assets/plant.png" alt="plant" />
</div>
<div class="upgrade-button" (click)="buy()" [class.disabled]="!canAfford()">
{{ getPrice() }} seeds
{{ getPriceFormatted() }} seeds
</div>
<div class="name">
{{ plant.getPlantData().farmer.name }}

View File

@@ -2,6 +2,7 @@ import {Component, EventEmitter, Input, OnInit, Output} from '@angular/core';
import {PlantDto} from "../plant.dto";
import {PlayerService} from "../player.service";
import Plant from "../Plant";
import {UtilitiesService} from "../utilities.service";
@Component({
selector: 'app-farmer-container',
@@ -11,12 +12,18 @@ import Plant from "../Plant";
export class FarmerContainerComponent implements OnInit {
@Input('plant') plant!: Plant;
constructor(private playerService: PlayerService) { }
constructor(
private playerService: PlayerService,
private utilitiesService: UtilitiesService,
) { }
getPrice() {
if (!this.plant) return 0;
return this.plant.getPlantData().farmer.price;
}
getPriceFormatted() {
return this.utilitiesService.formatNumber(this.getPrice());
}
buy() {
this.playerService.buyFarmer(this.plant.id);
}

View File

@@ -8,6 +8,7 @@ import { HomePageRoutingModule } from './home-routing.module';
import {PlantContainerComponent} from "../plant-container/plant-container.component";
import {ProgressBarComponent} from "../progress-bar/progress-bar.component";
import {FarmerContainerComponent} from "../farmer-container/farmer-container.component";
import {ProfileComponent} from "../profile/profile.component";
@NgModule({
@@ -17,6 +18,6 @@ import {FarmerContainerComponent} from "../farmer-container/farmer-container.com
IonicModule,
HomePageRoutingModule
],
declarations: [HomePage, PlantContainerComponent, ProgressBarComponent, FarmerContainerComponent]
declarations: [HomePage, PlantContainerComponent, ProgressBarComponent, FarmerContainerComponent, ProfileComponent]
})
export class HomePageModule {}

View File

@@ -2,8 +2,9 @@
<div id="game">
<div id="header">
<div id="profile-picture">
<img src="/assets/pfp-default.png"/>
<div id="profile-picture" (click)="activeTab = 10">
<app-profile></app-profile>
<!-- <img src="/assets/pfp-default.png"/>-->
</div>
<div id="currencies">
<div id="seeds">{{ getSeeds() }} Seeds</div>
@@ -14,11 +15,11 @@
</div>
</div>
<div id="navigation">
<div (click)="activeTab = 0">Plants</div>
<div (click)="activeTab = 1">Farmers</div>
<div>Boosters</div>
<div>Upgrades</div>
<div>Stats</div>
<div (click)="activeTab = 0" [class.active]="activeTab === 0">Plants</div>
<div (click)="activeTab = 1" [class.active]="activeTab === 1">Farmers</div>
<div (click)="activeTab = 2" [class.active]="activeTab === 2">Boosters</div>
<div (click)="activeTab = 3" [class.active]="activeTab === 3">Upgrades</div>
<div (click)="activeTab = 4" [class.active]="activeTab === 4">Stats</div>
</div>
<div id="plants" *ngIf="activeTab === 0">
<ng-container *ngFor="let plant of getPlants()">
@@ -34,5 +35,37 @@
></app-farmer-container>
</ng-container>
</div>
<div id="boosters" *ngIf="activeTab === 2">
Coming soon...
</div>
<div id="upgrades" *ngIf="activeTab === 3">
Coming soon...
</div>
<div id="stats" *ngIf="activeTab === 4">
Coming soon...
</div>
<div id="character" *ngIf="activeTab === 10">
<div class="pfp-display">
<app-profile></app-profile>
</div>
<div class="profile-category">
<h1>Hats</h1>
<div class="image-container">
<img *ngFor="let hat of getAllHats(); let i = index" [src]="hat ?? 'assets/blank.png'" (click)="setHat(i)" />
</div>
<div class="image-container">
<img *ngFor="let glasses of getAllGlasses(); let i = index" [src]="glasses ?? 'assets/blank.png'" (click)="setGlasses(i)" />
</div>
<div class="image-container">
<img *ngFor="let hoodie of getAllHoodies(); let i = index" [src]="hoodie ?? 'assets/blank.png'" (click)="setHoodie(i)" />
</div>
<div class="image-container">
<img *ngFor="let pants of getAllPants(); let i = index" [src]="pants ?? 'assets/blank.png'" (click)="setPants(i)" />
</div>
<div class="image-container">
<img *ngFor="let shoes of getAllShoes(); let i = index" [src]="shoes ?? 'assets/blank.png'" (click)="setShoes(i)" />
</div>
</div>
</div>
</div>
</ion-content>

View File

@@ -21,14 +21,10 @@
position: absolute;
top: 0;
left: 0;
height: calc((100vh / 6) - 4vh);
height: calc((100vh / 6));
aspect-ratio: calc(54/64);
img {
height: calc((100vh / 6) - 4vh);
aspect-ratio: calc(54/64);
margin: 2vh;
}
padding: 1vh;
}
#seeds {
@@ -75,7 +71,35 @@
width: 100%;
line-height: 3.6vh;
text-align: center;
&.active {
background: #3a7be0;
}
}
}
#boosters, #upgrades, #stats {
text-align: center;
color: black;
font-size: 2rem;
padding-top: 10vh;
}
#plants, #farmers, #character {
position: absolute;
top: calc(100vh / 6 + 3vh);
left: 0;
right: 0;
bottom: 0;
overflow-y: scroll;
}
.pfp-display {
width: 20vh;
magrin: 1vh;
}
.image-container {
display: grid;
grid-template-columns: repeat(5, 1fr);
}

View File

@@ -5,6 +5,7 @@ import {UtilitiesService} from "../utilities.service";
import {PlantDto} from "../plant.dto";
import {PlayerPlantDto} from "../playerPlant.dto";
import Plant from "../Plant";
import {ConfigService} from "../config.service";
@Component({
selector: 'app-home',
@@ -18,19 +19,61 @@ export class HomePage {
constructor(
private playerService: PlayerService,
private formulasService: FormulasService
private formulasService: FormulasService,
private readonly configService: ConfigService,
private readonly utilitiesService: UtilitiesService,
) {
this.gameLoop();
}
getSeeds(): number {
return this.playerService.seeds;
getSeeds(): string {
return this.utilitiesService.formatNumber(this.playerService.seeds);
}
getPlants(): Plant[] {
return this.playerService.plants;
}
getAllHats() {
return this.configService.getHats();
}
getAllGlasses() {
return this.configService.getGlasses();
}
getAllHoodies() {
return this.configService.getHoodies();
}
getAllPants() {
return this.configService.getPants();
}
getAllShoes() {
return this.configService.getShoes();
}
setHat(index: number) {
this.playerService.profilePicture.hat = index;
}
setGlasses(index: number) {
this.playerService.profilePicture.glasses = index;
}
setHoodie(index: number) {
this.playerService.profilePicture.hoodie = index;
}
setPants(index: number) {
this.playerService.profilePicture.pants = index;
}
setShoes(index: number) {
this.playerService.profilePicture.shoes = index;
}
gameLoop() {
const currentTime = Date.now();
@@ -42,7 +85,7 @@ export class HomePage {
plant.addTimePassed(dt);
if (plant.getFarberBought()) {
while (plant.getTimePassed() >= plant.getPlantData().timeToGrow) {
while (plant.getTimePassed() >= plant.getTimeToGrow()) {
this.playerService.seeds += plant.getRewardTotal();
plant.addTimePassed(-plant.getTimeToGrow());
}

View File

@@ -5,23 +5,23 @@
</div>
<div class="upgrade-button" (click)="plant.buy()" [class.disabled]="!plant.canAfford()">
Upgrade to Lv. {{ plant.getLevel() + 1 }}<br />
{{ plant.getPrice() }} seeds
{{ plant.getPriceFormatted() }} seeds
</div>
<div class="name">
{{ plant.getPlantData().name }}
</div>
<div class="reward">
{{ plant.getReward() }} seeds/plant
{{ plant.getRewardFormatted() }} seeds/plant
</div>
<app-progress-bar
[fill]="plant.progress()"
[fillColor]="'#FFD500'"
[backgroundColor]="'#005BBB'"
[fillFontColor]="'#005BBB'"
[backgroundFontColor]="'#FFD500'"
[progressBarText]="plant.timeLeft()"
[progressBarTextRight]="plant.getRewardTotalFormatted() + ' seeds'"
class="plant-progress"
></app-progress-bar>
<div class="reward-total">
+ {{ plant.getRewardTotal() }} seeds
</div>
<div class="ready-to-harvest" *ngIf="plant.getTimePassed() > plant.getTimeToGrow()">Ready To Harvest</div>
<div class="ready-to-harvest" *ngIf="plant.getTimePassed() > plant.getTimeToGrow() && !plant.isFarmerBought()">Ready To Harvest</div>
</div>

View File

@@ -14,6 +14,14 @@ export class PlayerService {
public lastTime = Date.now();
public seeds: number = 0;
public plants: Plant[] = [];
public profilePicture = {
hat: 0,
glasses: 0,
hoodie: 0,
pants: 0,
shoes: 0,
}
constructor(
private readonly formulasService: FormulasService,
private readonly utilitiesService: UtilitiesService,
@@ -28,6 +36,7 @@ export class PlayerService {
// this.plants = parsed.plants.map((plant: PlantDto) => ({...this.BlankPlant, ...plant}));
this.seeds = parsed.seeds;
this.profilePicture = parsed.profilePicture ?? this.profilePicture;
this.lastTime = parsed.lastTime ? parsed.lastTime : Date.now();
} else {
// add default plants
@@ -40,7 +49,8 @@ export class PlayerService {
localStorage.setItem('PlayerSave', JSON.stringify({
seeds: this.seeds,
plants: this.plants.map(plant => plant.getSaveData()),
lastTime: this.lastTime
lastTime: this.lastTime,
profilePicture: this.profilePicture,
}));
}

View File

@@ -0,0 +1,8 @@
<div class="profile">
<img src="assets/pfp-default.png" />
<img *ngIf="getHat()" [src]="getHat()" />
<img *ngIf="getHoodie()" [src]="getHoodie()" />
<img *ngIf="getShoes()" [src]="getShoes()" />
<img *ngIf="getPants()" [src]="getPants()" />
<img *ngIf="getGlasses()" [src]="getGlasses()" />
</div>

View File

@@ -0,0 +1,12 @@
.profile {
position: relative;
width: 100%;
aspect-ratio: calc(54/64);
img {
position: absolute;
top: 0;
left: 0;
width: 100%;
}
}

View File

@@ -0,0 +1,24 @@
import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
import { IonicModule } from '@ionic/angular';
import { ProfileComponent } from './profile.component';
describe('ProfileComponent', () => {
let component: ProfileComponent;
let fixture: ComponentFixture<ProfileComponent>;
beforeEach(waitForAsync(() => {
TestBed.configureTestingModule({
declarations: [ ProfileComponent ],
imports: [IonicModule.forRoot()]
}).compileComponents();
fixture = TestBed.createComponent(ProfileComponent);
component = fixture.componentInstance;
fixture.detectChanges();
}));
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@@ -0,0 +1,32 @@
import { Component, OnInit } from '@angular/core';
import {PlayerService} from "../player.service";
import {ConfigService} from "../config.service";
@Component({
selector: 'app-profile',
templateUrl: './profile.component.html',
styleUrls: ['./profile.component.scss'],
})
export class ProfileComponent implements OnInit {
constructor(private readonly playerService: PlayerService, private readonly configService: ConfigService) { }
ngOnInit() {}
public getHat() {
return this.configService.getHatByIndex(this.playerService.profilePicture.hat);
}
public getGlasses() {
return this.configService.getGlassesByIndex(this.playerService.profilePicture.glasses);
}
public getHoodie() {
return this.configService.getHoodieByIndex(this.playerService.profilePicture.hoodie);
}
public getPants() {
return this.configService.getPantsByIndex(this.playerService.profilePicture.pants);
}
public getShoes() {
return this.configService.getShoesByIndex(this.playerService.profilePicture.shoes);
}
}

View File

@@ -1,4 +1,8 @@
<div class="progress-bar-container" [style]="'background: ' + backgroundColor + ';'">
<div class="progress-bar-progress" [style]="'width: '+getFill()+'%; background: ' + fillColor + ';'"></div>
<div class="progress-bar-text">{{ progressBarText }}</div>
<div class="progress-bar-text" [style]="'color: ' + backgroundFontColor + ';'">{{ progressBarText }}</div>
<div class="progress-bar-text-right" [style]="'color: ' + backgroundFontColor + ';'">{{ progressBarTextRight }}</div>
<div class="progress-bar-progress" [style]="'width: '+getFill()+'%; background: ' + fillColor + ';'">
<div class="progress-bar-text" [style]="'color: ' + fillFontColor + ';'">{{ progressBarText }}</div>
<div class="progress-bar-text-right" [style]="'color: ' + fillFontColor + ';'">{{ progressBarTextRight }}</div>
</div>
</div>

View File

@@ -1,6 +1,6 @@
.progress-bar-container {
position: relative;
width: 100%;
width: calc(var(--game-width) - 1vh - 100vh / 8);
height: 100%;
.progress-bar-progress {
@@ -8,13 +8,24 @@
right: 0;
top: 0;
height: 100%;
overflow: hidden;
}
.progress-bar-text {
position: absolute;
width: 100%;
width: calc(var(--game-width) - 1vh - 100vh / 8);
height: 100%;
text-align: center;
line-height: 180%;
right: 0;
}
.progress-bar-text-right {
position: absolute;
width: calc(var(--game-width) - 1vh - 100vh / 8);
height: 100%;
text-align: right;
line-height: 180%;
right: 0;
}
}

View File

@@ -10,8 +10,11 @@ export class ProgressBarComponent implements OnInit {
@Input('fill') fill: number = 0;
@Input('progressBarText') progressBarText: string = '';
@Input('progressBarTextRight') progressBarTextRight: string = '';
@Input('backgroundColor') backgroundColor: string = '#1a1a1a';
@Input('fillColor') fillColor: string = '#2dd36f';
@Input('backgroundFontColor') backgroundFontColor: string = '#2dd36f';
@Input('fillFontColor') fillFontColor: string = '#1a1a1a';
constructor() { }

View File

@@ -30,6 +30,18 @@ export class UtilitiesService {
return (minutes > 9 ? minutes : '0' + minutes) + ':' + (seconds > 9 ? seconds : '0' + seconds);
}
return seconds + ' sec';
return '00:' + (seconds > 9 ? seconds : '0' + seconds);
}
formatNumber(num: number): string {
if (num < 1e3) return num.toString();
if (num < 1e6) return Math.round(num)/1e3 + 'k';
if (num < 1e9) return Math.floor(num/1e3)/1e3 + 'M';
if (num < 1e12) return Math.floor(num/1e6)/1e3 + 'B';
if (num < 1e15) return Math.floor(num/1e9)/1e3 + 'T';
if (num < 1e18) return Math.floor(num/1e12)/1e3 + 'aa';
if (num < 1e21) return Math.floor(num/1e15)/1e3 + 'ab';
if (num < 1e24) return Math.floor(num/1e18)/1e3 + 'ac';
return '0';
}
}

BIN
src/assets/blank.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

BIN
src/assets/glasses.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.6 KiB

BIN
src/assets/hat.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

BIN
src/assets/hoodie.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 18 KiB

After

Width:  |  Height:  |  Size: 16 KiB

BIN
src/assets/shoes.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.9 KiB

BIN
src/assets/shorts.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB