#define OLC_PGE_APPLICATION
#define OLC_SOUNDWAVE
#define OLC_PGEX_TRANSFORMEDVIEW
#define AUDIO_LISTENER_IMPLEMENTATION
#define AUDIO_SOURCE_IMPLEMENTATION
#define OLC_PGEX_QUICKGUI

#define SPLASH_ENABLED
#ifdef SPLASH_ENABLED
	#define OLC_PGEX_SPLASHSCREEN
#endif
#include "Scenario.h"
#include "TileManager.h"
#include "olcUTIL_Geometry2D.h"
#include "util.h"
#include "Level.h"
#include "Scenario.h"
#include "VirusAttack.h"

VirusAttack*game;
std::vector<std::unique_ptr<Renderable>>IMAGES;
std::vector<std::unique_ptr<Audio>>SOUNDS;
std::vector<std::unique_ptr<Scenario>>SCENARIOS;

VirusAttack::VirusAttack()
{
	// Name your application
	sAppName = "Virus Attack";
	game=this;
}

void VirusAttack::InitializeImages(){
	auto LoadImage=[&](Image img,std::string filepath,bool filter=false,bool clamp=true){
		IMAGES.emplace_back(std::make_unique<Renderable>());
		IMAGES[img]->Load(filepath,nullptr,filter,clamp);
	};
	LoadImage(TILE,"assets/tile.png",false,false);
	LoadImage(MINIMAP_HUD,"assets/minimap_hud.png");
	LoadImage(OUTLINE,"assets/outline.png");
	IMAGES.emplace_back(std::make_unique<Renderable>());
	IMAGES[MINIMAP_OUTLINE]->Create(64,64);
	LoadImage(VIRUS_IMG1,"assets/unit.png");
	LoadImage(SELECTION_CIRCLE,"assets/selection_circle.png");
	IMAGES.emplace_back(std::make_unique<Renderable>());
	IMAGES[MATRIX]->Create(64,64,false,false);
	IMAGES[MATRIX]->Sprite()->SetSampleMode(Sprite::PERIODIC);
	LoadImage(MEMORY_COLLECTION_POINT,"assets/memory_collection_point.png");
	LoadImage(LEFT_SHIFTER,"assets/left_shifter.png");
	LoadImage(RIGHT_SHIFTER,"assets/right_shifter.png");
	LoadImage(BIT_RESTORER,"assets/bit_restorer.png");
	LoadImage(MEMORY_SWAPPER,"assets/memory_swapper.png");
	LoadImage(CORRUPTER,"assets/corrupter.png");
	LoadImage(UNIT_ALLOCATOR,"assets/shell.png");
	LoadImage(RAM_BANK,"assets/ram_bank.png");
	LoadImage(RANGE_INDICATOR,"assets/range_indicator.png");
	LoadImage(DOWN_ARROW,"assets/down_arrow.png");
	LoadImage(RED_X,"assets/red_x.png");
	LoadImage(RLD,"assets/rld.png");
	LoadImage(PRC,"assets/prc.png");
	LoadImage(RNG,"assets/rng.png");
	LoadImage(SPD,"assets/spd.png");
	LoadImage(TARGETING_LINE,"assets/targetLine.png");
	LoadImage(ATTACKING_LINE,"assets/attackLine.png",false,false);
	LoadImage(RLD_ICON,"assets/rld_icon.png");
	LoadImage(PRC_ICON,"assets/prc_icon.png");
	LoadImage(RNG_ICON,"assets/rng_icon.png");
	LoadImage(SPD_ICON,"assets/spd_icon.png");
	LoadImage(RESOURCE,"assets/material.png");
	LoadImage(MEMORY_COLLECTION_POINT_HIGHLIGHT,"assets/memory_collection_point_highlight.png");
	LoadImage(MEMORY_GUARD,"assets/memoryguard.png");
	LoadImage(REFRESHER,"assets/refresher.png");
	LoadImage(TURRET,"assets/turret.png");
	LoadImage(PLATFORM,"assets/platform.png");
	LoadImage(GUARD_ICON,"assets/shieldIcon.png");
	LoadImage(GUIDE,"assets/guide.png");
	LoadImage(ROUND_BAR,"assets/round_bar.png");
	LoadImage(SEGMENT_BAR,"assets/segmentBar.png",false,false);
	LoadImage(HOODED_FIGURE,"assets/hooded_figure.png");
	LoadImage(SPOOK_HOODED_FIGURE,"assets/spook_hooded_figure.png");
	LoadImage(RESTART,"assets/restart.png");
	LoadImage(RESTART_HOVER,"assets/restartHover.png");
	LoadImage(SHIELD_BUBBLE,"assets/shieldbubble.png");

	titleScreenText.Create(426,100,false,true);
	SetDrawTarget(titleScreenText.Sprite());
	Clear(BLANK);
	float textScale=3;
	vf2d textSize=GetTextSizeProp("Virus Attack")*textScale;
	DrawShadowStringProp(titleScreenText.Sprite()->Size()/2-textSize/2,"Virus Attack",WHITE,BLACK,{textScale,textScale},textScale);
	SetDrawTarget(nullptr);
	titleScreenText.Decal()->Update();
	textOrientationX=titleScreenText.Sprite()->width/2;
	textOrientationY=titleScreenText.Sprite()->height-12;
}

void VirusAttack::InitializeLevelData(){
	#pragma region Stage 1
		{
			//Stage 1 data.
			LevelName stage=STAGE1;
			levelData[stage].name=stage;
			levelData[stage].cameraStart={96,96};
			levelData[stage].worldZoom={1,1};
			levelData[stage].levelColor=DARK_GREEN;
			levelData[stage].size={24,24};
			levelData[stage].bgm=Sound::GRAVITY;
			levelData[stage].scenarioIndex=int(stage);
			levelData[stage].availableMemory=240;
			levelData[stage].player_starting_resources={5,5,5,5,5};
			levelData[stage].enemy_starting_resources={0,0,0,0,0};
			{
				std::vector<UnitData>&units=levelData[stage].unitPlacement;
				std::vector<CPData>&collectionPoints=levelData[stage].cpPlacement;

				units.push_back({UnitType::LeftShifter,vf2d{128,128},true});
				units.push_back({UnitType::LeftShifter,vf2d{128,128},true});
				units.push_back({UnitType::RAMBank,vf2d{320,320},false});
			}
		}
	#pragma endregion
	#pragma region Stage 2
		{
			//Stage 2 data.
			LevelName stage=STAGE2;
			levelData[stage].name=stage;
			levelData[stage].cameraStart={96,96};
			levelData[stage].worldZoom={1,1};
			levelData[stage].size={30,30};
			levelData[stage].levelColor=DARK_GREEN;
			levelData[stage].bgm=Sound::GRAVITY;
			levelData[stage].scenarioIndex=int(stage);
			levelData[stage].availableMemory=240;
			levelData[stage].player_starting_resources={50,30,40,30,55};
			levelData[stage].enemy_starting_resources={0,0,0,0,0};
			{
				std::vector<UnitData>&units=levelData[stage].unitPlacement;
				std::vector<CPData>&collectionPoints=levelData[stage].cpPlacement;

				units.push_back({UnitType::_Platform,vf2d{7*24,10*24},true});

				units.push_back({UnitType::RightShifter,vf2d{20*24,21*24},false});
				units.push_back({UnitType::LeftShifter,vf2d{20*24,22*24},false});
				units.push_back({UnitType::MemoryAllocator,vf2d{22*24,24*24},false});
				units.push_back({UnitType::RAMBank,vf2d{22*24,23*24},false});
			}
	}
	#pragma endregion
}

bool VirusAttack::OnUserCreate(){
	srand(time(NULL));
	currentBackCol=newCol=colorChangeOptions[rand()%colorChangeOptions.size()];
	SetPixelMode(Pixel::MASK);

	gametv.Initialise(GetScreenSize());

	InitializeImages();

	unitCreationBox.Initialize("Hello world, this is a test of the textbox system.\nMaybe even with some newline characters snuck in there.",
		{});
	unitCreationBox.SetVisible(false);
	testBox.Initialize("Hello world, this is a test of the textbox system.\nMaybe even with some newline characters snuck in there.",{});
	testBox.SetVisible(false);
	memoryAllocatorBox.Initialize(CONSTANT::MEMORY_ALLOCATOR_BOX_DISPLAY_STRING,{},CONSTANT::MEMORY_ALLOCATOR_BOX_HEADER_STRING);
	memoryAllocatorBox.SetVisible(false);
	platformCreationBox.SetVisible(false);
	restartBox.SetVisible(false);

	attackingLineModified.Create(IMAGES[ATTACKING_LINE]->Sprite()->width,IMAGES[ATTACKING_LINE]->Sprite()->height,false,false);

	AL.AudioSystemInit();
	InitializeSounds();

	InitializeGUIs();
	InitializeScenarios();
	InitializeLevelData();

	LoadLevel(STAGE1);
	return true;
}

void VirusAttack::LoadLevel(LevelName level){
	Level&selectedLevel=levelData[level];

	currentLevel=&selectedLevel;
	currentScenario=currentLevel->scenarioIndex;

	WORLD_SIZE=selectedLevel.size;

	gametv.SetWorldScale(selectedLevel.worldZoom);
	gametv.SetWorldOffset(selectedLevel.cameraStart-vf2d(GetScreenSize())/2.f/gametv.GetWorldScale());

	if(bgm==nullptr){
		bgm=SOUNDS[int(selectedLevel.bgm)].get();
		bgm->PlayCentered(1,1,true);
	} else
	if(bgm!=SOUNDS[int(selectedLevel.bgm)].get()){
		bgm->Stop();
		bgm=SOUNDS[int(selectedLevel.bgm)].get();
		bgm->PlayCentered(1,1,true);
	}
	player_resources=selectedLevel.player_starting_resources;
	enemy_resources=selectedLevel.enemy_starting_resources;

	TileManager::visibleTiles.clear();
	std::for_each(units.begin(),units.end(),[&](auto&u){
		u->OnDeath(SOUNDS);
	});
	units.clear();
	collectionPoints.clear();

	for(auto&u:selectedLevel.unitPlacement){
		#define TranslateUnit(type) \
			case UnitType::type:{ \
				units.push_back(std::make_shared<type>(this,u.pos,IMAGES,u.friendly)); \
			}break;

		switch(u.type){
			TranslateUnit(LeftShifter)
			TranslateUnit(RightShifter)
			TranslateUnit(BitRestorer)
			TranslateUnit(MemorySwapper)
			TranslateUnit(Corrupter)
			TranslateUnit(MemoryAllocator)
			TranslateUnit(RAMBank)
			TranslateUnit(_Platform)
			TranslateUnit(MemoryGuard)
			TranslateUnit(Refresher)
			TranslateUnit(Turret)
			default:{
				std::cout<<"Could not load unit type "<<int(u.type)<<"!"<<std::endl;
				throw;
			}
		}
	}
	for(auto&cp:selectedLevel.cpPlacement){
		collectionPoints.push_back(std::make_shared<CollectionPoint>(this,cp.pos,cp.rot,*IMAGES[MEMORY_COLLECTION_POINT],cp.type));	
	}

	randomBackgroundOffset={util::random(128),util::random(128)};

	objective="";
	SCENARIOS[currentScenario]->_Start();
}

void VirusAttack::InitializeGUIs(){
	restartButton=new QuickGUI::TransparentImageButton(restartManager,*IMAGES[RESTART],*IMAGES[RESTART_HOVER],{1,1},{float(ScreenWidth()-32),12.f},{28,28});

	unitCreationList.colNormal = olc::DARK_GREEN;
	unitCreationList.colHover = olc::GREEN;
	unitCreationList.colClick = olc::YELLOW;
	unitCreationList.colDisable = olc::DARK_GREY;
	unitCreationList.colBorder = olc::YELLOW;
	unitCreationList.colText = olc::WHITE;
	platformCreationList.CopyThemeFrom(unitCreationList);
	leftShifterButton=new QuickGUI::ImageButton(unitCreationList,*IMAGES[LEFT_SHIFTER],{0.5,0.5},{16.f+32*0,float(ScreenHeight()-48)},{20,20});
	rightShifterButton=new QuickGUI::ImageButton(unitCreationList,*IMAGES[RIGHT_SHIFTER],{0.5,0.5},{16.f+32*1,float(ScreenHeight()-48)},{20,20});
	bitRestorerButton=new QuickGUI::ImageButton(unitCreationList,*IMAGES[BIT_RESTORER],{0.5,0.5},{16.f+32*2,float(ScreenHeight()-48)},{20,20});
	memorySwapperButton=new QuickGUI::ImageButton(unitCreationList,*IMAGES[MEMORY_SWAPPER],{0.5,0.5},{16.f+32*3,float(ScreenHeight()-48)},{20,20});
	corrupterButton=new QuickGUI::ImageButton(unitCreationList,*IMAGES[CORRUPTER],{0.5,0.5},{16.f+32*4,float(ScreenHeight()-48)},{20,20});
	platformButton=new QuickGUI::ImageButton(unitCreationList,*IMAGES[PLATFORM],{0.25,0.25},{16.f+32*5,float(ScreenHeight()-48)},{20,20});
	
	ramBankButton=new QuickGUI::ImageButton(platformCreationList,*IMAGES[RAM_BANK],{0.125,0.125},{float(ScreenWidth()-48),48.f+32*0},{20,20});
	refresherButton=new QuickGUI::ImageButton(platformCreationList,*IMAGES[REFRESHER],{0.25,0.25},{float(ScreenWidth()-48),48.f+32*1},{20,20});
	turretButton=new QuickGUI::ImageButton(platformCreationList,*IMAGES[TURRET],{0.25,0.25},{float(ScreenWidth()-48),48.f+32*2},{20,20});
	memoryGuardButton=new QuickGUI::ImageButton(platformCreationList,*IMAGES[MEMORY_GUARD],{0.25,0.25},{float(ScreenWidth()-48),48.f+32*3},{20,20});

	unitCreationList.DisplayAllControls(false);
	platformCreationList.DisplayAllControls(false);

	campaignStartButton=new QuickGUI::TransparentButton(mainMenu,"Start Campaign",{float(ScreenWidth()/2)-120.f,80+47.5f*0+10},{240,24},CONSTANT::INCREASE_VALUE_COLOR);
	audioToggleButton=new QuickGUI::TransparentButton(mainMenu,"Audio: On",{float(ScreenWidth()/2)-120.f,80+47.5f*1+10},{240,24},CONSTANT::INCREASE_VALUE_COLOR);
	difficultyToggleButton=new QuickGUI::TransparentButton(mainMenu,"Difficulty: Normal",{float(ScreenWidth()/2)-120.f,80+47.5f*2+10},{240,24},CONSTANT::INCREASE_VALUE_COLOR);
	creditsButton=new QuickGUI::TransparentButton(mainMenu,"Credits",{float(ScreenWidth()/2)-120.f,80+47.5f*3+10},{240,24},CONSTANT::INCREASE_VALUE_COLOR);
	exitGameButton=new QuickGUI::TransparentButton(mainMenu,"Exit Game",{float(ScreenWidth()/2)-120.f,80+47.5f*4+10},{240,24},CONSTANT::INCREASE_VALUE_COLOR);
}

void VirusAttack::InitializeScenarios(){
	SCENARIOS.emplace_back(std::make_unique<Stage1>(units,IMAGES,SOUNDS,objective,gametv,flags));
	SCENARIOS.emplace_back(std::make_unique<Stage2>(units,IMAGES,SOUNDS,objective,gametv,flags));
	SCENARIOS.emplace_back(std::make_unique<Stage3>(units,IMAGES,SOUNDS,objective,gametv,flags));
	SCENARIOS.emplace_back(std::make_unique<Stage4>(units,IMAGES,SOUNDS,objective,gametv,flags));
	SCENARIOS.emplace_back(std::make_unique<Stage5>(units,IMAGES,SOUNDS,objective,gametv,flags));
	SCENARIOS.emplace_back(std::make_unique<Stage6>(units,IMAGES,SOUNDS,objective,gametv,flags));
	SCENARIOS.emplace_back(std::make_unique<Stage7>(units,IMAGES,SOUNDS,objective,gametv,flags));
	SCENARIOS.emplace_back(std::make_unique<Stage8>(units,IMAGES,SOUNDS,objective,gametv,flags));
}

void VirusAttack::InitializeSounds(){
	int soundIndex=0;
	auto LoadSound=[&](Sound::Sound sound,std::string soundFilename){
		SOUNDS.emplace_back(std::make_unique<Audio>());
		SOUNDS[int(sound)]->AL=&AL;
		SOUNDS[int(sound)]->LoadAudioSample(soundIndex,std::string("./assets/"+soundFilename).c_str());
		soundIndex++;
	};

	LoadSound(Sound::HUM,"machine2.wav");
	LoadSound(Sound::GRAVITY,"gravity.mp3");
	LoadSound(Sound::COSMOS,"cosmos.mp3");
	LoadSound(Sound::BOSS1,"boss1.mp3");
	LoadSound(Sound::BOSS2,"boss2.mp3");
	LoadSound(Sound::VOICEOVER,"voice.mp3");
	LoadSound(Sound::PING,"ping.mp3");
	LoadSound(Sound::ALARM,"alarm.mp3");
}

bool VirusAttack::UnitCreationClickHandled(){
	#define CheckClick(UnitClass,Button,Validator)  \
		if(Button->bPressed){ \
			for(auto&u:units){ \
				if(u->IsSelected()&&u->Validator()&&CanAfford(player_resources,UnitClass::resourceCost)) { \
					std::shared_ptr<UnitClass>buildUnit=std::make_shared<UnitClass>(this,u->GetPos(),IMAGES,u->IsFriendly()); \
					u->SetBuildUnit(CONSTANT::UNIT_BUILD_TIME,std::move(buildUnit)); \
					ExpendResources(player_resources,UnitClass::resourceCost); \
					CalculateUsedMemory(); \
				} \
			} \
			return true; \
		}

	CheckClick(LeftShifter,leftShifterButton,IsAllocator)
	CheckClick(RightShifter,rightShifterButton,IsAllocator)
	CheckClick(BitRestorer,bitRestorerButton,IsAllocator)
	CheckClick(MemorySwapper,memorySwapperButton,IsAllocator)
	CheckClick(BitRestorer,bitRestorerButton,IsAllocator)
	CheckClick(Corrupter,corrupterButton,IsAllocator)
	CheckClick(_Platform,platformButton,IsAllocator)
	CheckClick(RAMBank,ramBankButton,IsPlatform)
	CheckClick(Refresher,refresherButton,IsPlatform)
	CheckClick(Turret,turretButton,IsPlatform)
	CheckClick(MemoryGuard,memoryGuardButton,IsPlatform)
	return false;	
}
#define EnableAndHoverCheck(UnitClass,Button,box,allowed) \
	Button->Enable(CanAfford(player_resources,UnitClass::resourceCost)&&allowed); \
	if(!allowed){Button->bVisible=false;} \
	if(Button->bHover){ \
		box.Initialize(UnitClass::unitDescription, GetMousePos(), UnitClass::unitName,nullptr,{120,36},nullptr,UnitClass::resourceCost); \
		hovering=true; \
		if(CanAfford(player_resources,UnitClass::resourceCost)){ \
			box.SetBackgroundColor(CONSTANT::MESSAGE_BOX_DEFAULT_BACKCOL); \
		} else { \
			box.SetBackgroundColor(VERY_DARK_GREY/2); \
		} \
	}

void VirusAttack::UpdateUnitCreationListGUI(bool allocatorSelected){
	unitCreationList.DisplayAllControls(allocatorSelected);

	bool hovering=false;
	EnableAndHoverCheck(LeftShifter,leftShifterButton,unitCreationBox,true)
	EnableAndHoverCheck(RightShifter,rightShifterButton,unitCreationBox,true)
	EnableAndHoverCheck(BitRestorer,bitRestorerButton,unitCreationBox,!flags.limitedBuildOptions)
	EnableAndHoverCheck(MemorySwapper,memorySwapperButton,unitCreationBox,!flags.limitedBuildOptions)
	EnableAndHoverCheck(BitRestorer,bitRestorerButton,unitCreationBox,!flags.limitedBuildOptions)
	EnableAndHoverCheck(Corrupter,corrupterButton,unitCreationBox,!flags.limitedBuildOptions)
	EnableAndHoverCheck(_Platform,platformButton,unitCreationBox,!flags.limitedBuildOptions)

	if(!hovering){
		unitCreationBox.SetVisible(false);
	}

	unitCreationList.Update(this);
}

void VirusAttack::UpdatePlatformCreationListGUI(bool platformSelected){
	platformCreationList.DisplayAllControls(platformSelected);
	bool hovering=false;
	EnableAndHoverCheck(RAMBank,ramBankButton,platformCreationBox,true)
	EnableAndHoverCheck(Refresher,refresherButton,platformCreationBox,!flags.limitedBuildOptions)
		EnableAndHoverCheck(Turret,turretButton,platformCreationBox,!flags.limitedBuildOptions)
		EnableAndHoverCheck(MemoryGuard,memoryGuardButton,platformCreationBox,!flags.limitedBuildOptions)

	if(!hovering){
		platformCreationBox.SetVisible(false);
	}

	platformCreationList.Update(this);
}

void VirusAttack::HandleDraggingSelection(){
	auto NotClickingOnMinimap=[&](){return !(GetMouseX()>=ScreenWidth()-64&&GetMouseY()>=ScreenHeight()-64);};
	if(GetMouse(0).bPressed){
		if(NotClickingOnMinimap()){
			for(auto&u:units){
				if(u->ClickHandled(gametv,player_resources,units,IMAGES)){
					goto skipLeftClick; //Break out early because this instance reported it handled a click for us.
				}
			}
			if(UnitCreationClickHandled()){
				goto skipLeftClick;
			}
			for(auto&u:units){
				u->Deselect();
			}
			if(startingDragPos==CONSTANT::UNSELECTED){
				startingDragPos=GetWorldMousePos();
			}
		}
	}
	skipLeftClick:
	if(GetMouse(0).bReleased&&startingDragPos!=CONSTANT::UNSELECTED){
		vf2d endDragPos=GetWorldMousePos();
		if(endDragPos.x<startingDragPos.x){std::swap(startingDragPos.x,endDragPos.x);}
		if(endDragPos.y<startingDragPos.y){std::swap(startingDragPos.y,endDragPos.y);}
		geom2d::rect<float> selectionRegion(startingDragPos,endDragPos-startingDragPos);
		for(auto&u:units){
			if(u->IsFriendly()){
				if(geom2d::overlaps(selectionRegion,geom2d::circle<float>(u->GetPos(),u->GetUnitSize().x/2))){
					u->Select();
				}
			}
		}
		startingDragPos=CONSTANT::UNSELECTED;
	}
}

vf2d VirusAttack::GetWorldMousePos(){	
	return gametv.ScreenToWorld(GetMousePos());
}

void VirusAttack::DrawSelectionRectangle(){
	if(startingDragPos!=CONSTANT::UNSELECTED){
		gametv.FillRectDecal(startingDragPos,GetWorldMousePos()-startingDragPos,{255,255,0,128});
	}
}

void VirusAttack::HandleRightClickMove(){
	if (GetMouse(1).bHeld){
		bool selectedTarget=false;
		if(GetMouseX()>=ScreenWidth()-64&&GetMouseY()>=ScreenHeight()-64){//Clicked on Minimap.
			for(auto&u:units){
				if(u->IsFriendly()&&u->IsSelected()&&u->CanMove()){
					vf2d minimapTL=GetScreenSize()-vf2d{64,64};
					vf2d minimapCenterClick=vf2d(GetMousePos()-minimapTL)/64*WORLD_SIZE*CONSTANT::TILE_SIZE-vf2d(GetScreenSize())/gametv.GetWorldScale()/2.f+vf2d(GetScreenSize()/2.f)/gametv.GetWorldScale();
					u->SetTargetLocation(minimapCenterClick);
				}
			}
			goto targetFound;
		}
		for(auto&u:units){
			if(geom2d::overlaps(geom2d::circle<float>(u->GetPos(),u->GetUnitSize().x/2),GetWorldMousePos())){
				for(auto&u2:units){
					if(&u!=&u2){
						if(!u->IsFriendly()&&u2->IsFriendly()&&u2->IsSelected()&&u2->CanInteractWithEnemies()){
							u2->SetTargetUnit(u);
						} else
						if(u->IsFriendly()&&u2->IsFriendly()&&u2->IsSelected()&&u2->CanInteractWithAllies()){
							u2->SetTargetUnit(u);
						}
					}
				}
				selectedTarget=true;
				break;
			}
		}
		if(!selectedTarget){
			for(auto&u:units){
				if(u->IsFriendly()&&u->IsSelected()&&u->CanMove()){
					//First see if we can attach to a collection point.
					for(auto&cp:collectionPoints){
						geom2d::rect<float>cpRect=geom2d::rect<float>({cp->pos-cp->img.Sprite()->Size()/2,cp->img.Sprite()->Size()});
						if(geom2d::overlaps(cpRect,GetWorldMousePos())){
							if(cp->attachedUnit.expired()){
								u->SetTargetCollectionPoint(cp,u);
								goto targetFound; //We found a target, so now we can just leave.
							}
						}
					}

					//Okay, nothing to do here. Simply move to the selected position.
					u->SetTargetLocation(GetWorldMousePos());
				}
			}
		}
		targetFound:
		int a;
	}
}

void VirusAttack::CollisionChecking(std::shared_ptr<Unit>u,std::shared_ptr<Unit>u2){
	if(u!=u2&&geom2d::overlaps(geom2d::circle<float>(u->GetPos(),u->GetUnitSize().x/2),geom2d::circle<float>(u2->GetPos(),u2->GetUnitSize().x/2))){
		geom2d::line<float>collisionLine(u->GetPos(),u2->GetPos()+vf2d{0.001,0.001});
		float maxDist=u->GetUnitSize().x/2+u2->GetUnitSize().x/2;
		float dist=maxDist-collisionLine.length();
		vf2d dir=collisionLine.vector().norm();
		if(u->IsMoveable()||(!u->IsMoveable()&&!u2->IsMoveable())){
			u->SetPos(u->GetPos()-dir*dist/2);
		}
		if(u2->IsMoveable()||(!u->IsMoveable()&&!u2->IsMoveable())){
			u2->SetPos(u2->GetPos()+dir*(dist)/2);
		}
	}
}

void VirusAttack::IdentifyClosestTarget(std::weak_ptr<Unit>&closestUnit,float&closestDist,std::shared_ptr<Unit>u,std::shared_ptr<Unit>u2){
	if(u==u2)return;
	bool canInteract;
	canInteract=
		(u->IsFriendly()&&u->CanInteractWithEnemies()&&!u2->IsFriendly())||
		(u->IsFriendly()&&u->CanInteractWithAllies()&&u2->IsFriendly()&&u->AutoAcquiresFriendlyTargets())||
		(!u->IsFriendly()&&u->CanInteractWithEnemies()&&u2->IsFriendly())||
		(!u->IsFriendly()&&u->CanInteractWithAllies()&&!u2->IsFriendly()&&u->AutoAcquiresFriendlyTargets());
	if(canInteract){
		geom2d::line<float>unitLine(u->GetPos(),u2->GetPos());
		if(unitLine.length()<closestDist){
			closestUnit=u2;
			closestDist=unitLine.length();
		}
	}
}

void VirusAttack::DrawMinimap(){
	DrawDecal(GetScreenSize()-IMAGES[MINIMAP_HUD]->Sprite()->Size(),IMAGES[MINIMAP_HUD]->Decal(),{1,1});
	vf2d minimapTL=GetScreenSize()-vf2d{64,64};
	vi2d worldPixelSize=WORLD_SIZE*CONSTANT::TILE_SIZE;
	vf2d viewingTilesPct=vf2d{float(ScreenWidth()),float(ScreenHeight())}/CONSTANT::TILE_SIZE/WORLD_SIZE;
	SetDrawTarget(IMAGES[MINIMAP_OUTLINE]->Sprite());
	Clear(BLANK);
	DrawRect((gametv.GetWorldOffset()/worldPixelSize*64),viewingTilesPct*64/gametv.GetWorldScale());
	for(auto&cp:collectionPoints){
		Pixel col;
		switch(cp->type){
			case HEALTH:{
				col=CONSTANT::HEALTH_COLOR;
			}break;
			case ATKSPD:{
				col=CONSTANT::ATKSPD_COLOR;
			}break;
			case MOVESPD:{
				col=CONSTANT::MOVESPD_COLOR;
			}break;
			case RANGE:{
				col=CONSTANT::RANGE_COLOR;
			}break;
			case PROCEDURE:{
				col=CONSTANT::PROCEDURE_COLOR;
			}break;
		}
		FillCircle(cp->pos/worldPixelSize*64-vf2d{1,0},1,col);
		DrawCircle(cp->pos/worldPixelSize*64-vf2d{1,0},1,BLACK);
	}
	for(auto&u:units){
		vf2d squareSize=u->GetUnitSize()/vf2d(WORLD_SIZE);
		squareSize.x=std::round(std::max(1.f,squareSize.x));
		squareSize.y=std::round(std::max(1.f,squareSize.y));
		float colorMult=1;
		if(u->IsAttached()){colorMult=0.5;}
		FillRect(u->GetGhostPos()/worldPixelSize*64-squareSize,squareSize*2,(u->IsFriendly()?GREEN:RED)*colorMult);
	}
	IMAGES[MINIMAP_OUTLINE]->Decal()->Update();
	SetDrawTarget(nullptr);
	DrawDecal(minimapTL,IMAGES[MINIMAP_OUTLINE]->Decal());
}

void VirusAttack::HandlePanAndZoom(float fElapsedTime){
	float speedScale=std::min(1.f,gametv.GetWorldScale().x);
	bool canMouseScroll=!memoryAllocatorBox.IsVisible()&&!unitCreationBox.IsVisible()&&
		(GetMouseY()<=ScreenHeight()-64||GetMouseX()<=ScreenWidth()-64
			||GetMouseScreenX()>=GetWindowPos().x+GetWindowSize().x||GetMouseScreenY()>=GetWindowPos().y+GetWindowSize().y)&&!restartBox.IsVisible();
	if(GetKey(A).bHeld||canMouseScroll&&GetMouseScreenX()<=GetWindowPos().x+CONSTANT::SCROLL_BOUNDARY){
		vf2d amt=vf2d{-300*fElapsedTime,0}/speedScale;
		gametv.MoveWorldOffset(amt);
	}
	if(GetKey(W).bHeld||canMouseScroll&&GetMouseScreenY()<=GetWindowPos().y+CONSTANT::SCROLL_BOUNDARY+24){
		gametv.MoveWorldOffset(vf2d{0,-300*fElapsedTime}/speedScale);
	}
	if(GetKey(S).bHeld||canMouseScroll&&GetMouseScreenY()>=GetWindowPos().y+GetWindowSize().y-CONSTANT::SCROLL_BOUNDARY){
		gametv.MoveWorldOffset(vf2d{0,300*fElapsedTime}/speedScale);
	}
	if(GetKey(D).bHeld||canMouseScroll&&GetMouseScreenX()>=GetWindowPos().x+GetWindowSize().x-CONSTANT::SCROLL_BOUNDARY){
		vf2d amt=vf2d{300*fElapsedTime,0}/speedScale;
		gametv.MoveWorldOffset(amt);
	}

	if(GetMouseWheel()>0){
		if(gametv.GetWorldScale().x<2){
			gametv.ZoomAtScreenPos(1.25,GetMousePos());
		}
	} else
	if(GetMouseWheel()<0){
		if(gametv.GetWorldScale().x>0.5){
			gametv.ZoomAtScreenPos(0.75,GetMousePos());
		}
	}
}

void VirusAttack::HandleMinimapClick(){
	if(startingDragPos==CONSTANT::UNSELECTED&&GetMouse(0).bHeld&&GetMouseX()>=ScreenWidth()-64&&GetMouseY()>=ScreenHeight()-64){
		vf2d minimapTL=GetScreenSize()-vf2d{64,64};
		gametv.SetWorldOffset(vf2d(GetMousePos()-minimapTL)/64*WORLD_SIZE*CONSTANT::TILE_SIZE-vf2d(GetScreenSize())/gametv.GetWorldScale()/2.f);
	}
	vf2d offset=gametv.GetWorldOffset();
	offset.x=std::clamp(float(offset.x),-ScreenWidth()/2/gametv.GetWorldScale().x,WORLD_SIZE.x*24-(ScreenWidth()/2/gametv.GetWorldScale().x));
	offset.y=std::clamp(float(offset.y),-ScreenHeight()/2/gametv.GetWorldScale().y,WORLD_SIZE.y*24-(ScreenHeight()/2/gametv.GetWorldScale().y));
	gametv.SetWorldOffset(offset);

}

void VirusAttack::UpdateMatrixTexture(float fElapsedTime){
	if(matrixTimer==0){
		activeLetters.emplace_back(vf2d{float(rand()%64),float(64)},util::random(-40)-20,matrixLetters[rand()%matrixLetters.size()]);
		matrixTimer=util::random(0.125);
	}
	if(updatePixelsTimer==0){
		SetDrawTarget(IMAGES[MATRIX]->Sprite());
		Sprite*img=IMAGES[MATRIX]->Sprite();
		for(int y=63;y>=0;y--){
			for(int x=63;x>=0;x--){
				Pixel col=img->GetPixel(x,y);
				if(col.r>0){
					if(x>0){
						Pixel leftCol=img->GetPixel(x-1,y);
						if(leftCol.r<col.r){
							leftCol=PixelLerp(col,leftCol,0.125);
						}
						Draw(x-1,y,leftCol);
					}
					if(x<img->width-1){
						Pixel rightCol=img->GetPixel(x+1,y);
						if(rightCol.r<col.r){
							rightCol=PixelLerp(col,rightCol,0.125);
						}
						Draw(x+1,y,rightCol);
					}
					col/=8;
					Draw(x,y,col);
				}
			}
		}
		for(int y=0;y<64;y++){
			Draw({0,y},img->GetPixel(1,y));
		}
		SetDrawTarget(nullptr);
		updatePixelsTimer=0.1;
	}
	if(activeLetters.size()>0){
		SetDrawTarget(IMAGES[MATRIX]->Sprite());
		for(Letter&letter:activeLetters){
			letter.pos.y+=letter.spd*fElapsedTime;
			DrawString(letter.pos,std::string(1,letter.c));
		}
		SetDrawTarget(nullptr);
		IMAGES[MATRIX]->Decal()->Update();
	}
	matrixTimer=std::max(0.f,matrixTimer-fElapsedTime);
	updatePixelsTimer=std::max(0.f,updatePixelsTimer-fElapsedTime);
	std::erase_if(activeLetters,[](Letter&letter){return letter.pos.y<-32;});
}

void VirusAttack::RenderCollectionPoints(CollectionPoint*cp){
	geom2d::rect<float>cpRect=geom2d::rect<float>({cp->pos-cp->img.Sprite()->Size()/2,cp->img.Sprite()->Size()});
	geom2d::rect<float>viewRegion=geom2d::rect<float>({gametv.GetWorldTL(),gametv.GetWorldVisibleArea()});
	if(geom2d::overlaps(cpRect,viewRegion)){
		Pixel col;
		switch(cp->type){
			case HEALTH:{
				col=CONSTANT::HEALTH_COLOR;
			}break;
			case RANGE:{
				col=CONSTANT::RANGE_COLOR;
			}break;
			case ATKSPD:{
				col=CONSTANT::ATKSPD_COLOR;
			}break;
			case MOVESPD:{
				col=CONSTANT::MOVESPD_COLOR;
			}break;
			case PROCEDURE:{
				col=CONSTANT::PROCEDURE_COLOR;
			}break;
		}
		gametv.DrawRotatedDecal(cp->pos,cp->img.Decal(),cp->rot,cp->img.Sprite()->Size()/2,{1,1},col);
		if(geom2d::overlaps(cpRect,GetWorldMousePos())){
			gametv.DrawRotatedDecal(cp->pos,IMAGES[MEMORY_COLLECTION_POINT_HIGHLIGHT]->Decal(),cp->rot,cp->img.Sprite()->Size()/2,{1,1},col);
		}
	}
}

bool VirusAttack::OnUserUpdate(float fElapsedTime){
	UpdateMatrixTexture(fElapsedTime);
	switch(state){
		#pragma region MAIN_MENU
			case GameState::MAIN_MENU:{
				mainMenu.Update(this);

				if(campaignStartButton->bPressed){
					state=GameState::GAMEPLAY;
					RestartLevel();
				}

				titleScreenY=std::min(0.f,titleScreenY+fElapsedTime*120);
				nextColorChange=std::max(0.f,nextColorChange-fElapsedTime);
				transition=std::min(1.f,transition+fElapsedTime);
				if(nextColorChange==0){
					currentBackCol=newCol;
					newCol=colorChangeOptions[rand()%colorChangeOptions.size()];
					randomBackgroundOffset={util::random(128),util::random(128)};
					nextColorChange=10;
					transition=0;
				}
				if(titleScreenY==0){
					textOrientationX=std::max(0.f,textOrientationX-fElapsedTime*50);
					textOrientationY=std::max(0.f,textOrientationY-fElapsedTime*25);
				}
				DrawPartialDecal({0,0},GetScreenSize(),IMAGES[MATRIX]->Decal(),randomBackgroundOffset+gametv.GetWorldOffset()*(vf2d{32,32}/vf2d(GetScreenSize()))*gametv.GetWorldScale(),{32,32},PixelLerp(Pixel{currentBackCol.r,currentBackCol.g,currentBackCol.b,255},Pixel{newCol.r,newCol.g,newCol.b,255},transition));

				vf2d setPieceOffset={48,titleScreenY};
				{
					vf2d offset=vf2d{0,0}+setPieceOffset;
					DrawWarpedDecal(IMAGES[RAM_BANK]->Decal(),{vf2d{24,24}+offset,vf2d{24,104}+offset,vf2d{72,88}+offset,vf2d{72,40}+offset},Pixel{192,192,255});
				}
				{
					vf2d offset=vf2d{100,-52}+setPieceOffset;
					DrawWarpedDecal(IMAGES[MEMORY_GUARD]->Decal(),{vf2d{20,48}+offset,vf2d{20,108}+offset,vf2d{96,104}+offset,vf2d{96,52}+offset},Pixel{192,192,255});
				}
				{
					vf2d offset=vf2d{76,-20}+setPieceOffset;
					DrawWarpedDecal(IMAGES[BIT_RESTORER]->Decal(),{vf2d{12,52}+offset,vf2d{12,104}+offset,vf2d{64,92}+offset,vf2d{64,64}+offset},Pixel{192,192,255});
				}
				DrawCurvedTexture(vf2d{170-48,70}+setPieceOffset,{-30,-20},attackingLineModified.Decal(),{arrowScroll/2,0},CONSTANT::HEALER_ATTACK_COL,1.5);
				{
					vf2d offset=vf2d{48,0}+setPieceOffset;
					DrawWarpedDecal(IMAGES[LEFT_SHIFTER]->Decal(),{vf2d{20,48}+offset,vf2d{20,108}+offset,vf2d{68,96}+offset,vf2d{68,60}+offset},Pixel{192,192,255});
				}
				if(flickerAmt>0.6){
					flickerAmt-=util::random(0.4);
				} else {
					flickerAmt=1;
				}
				DrawDecal(vf2d{12,-28}+setPieceOffset,IMAGES[SHIELD_BUBBLE]->Decal(),{2.4,2.8},{255,255,255,uint8_t(flickerAmt*255)});
				{
					vf2d offset=vf2d{250,-18}+setPieceOffset;
					DrawWarpedDecal(IMAGES[RAM_BANK]->Decal(),{vf2d{8,38}+offset,vf2d{8,90}+offset,vf2d{68,102}+offset,vf2d{68,26}+offset},Pixel{255,192,192});
				}
				{
					vf2d offset=vf2d{220,0}+setPieceOffset;
					DrawWarpedDecal(IMAGES[MEMORY_SWAPPER]->Decal(),{vf2d{20,60}+offset,vf2d{20,96}+offset,vf2d{68,108}+offset,vf2d{68,48}+offset},Pixel{255,192,192});
				}
				arrowScroll-=fElapsedTime*2;
				SetDrawTarget(attackingLineModified.Sprite());
				Clear(BLANK);
				for(int y=0;y<IMAGES[ATTACKING_LINE]->Sprite()->height;y++){
					for(int x=0;x<IMAGES[ATTACKING_LINE]->Sprite()->width;x++){
						Pixel col=IMAGES[ATTACKING_LINE]->Sprite()->GetPixel(x,y);
						if(col.a>0){
							if(col==WHITE){
								Draw(x,y,IMAGES[MATRIX]->Sprite()->GetPixel(x,y));
							}else{
								Draw(x,y,IMAGES[ATTACKING_LINE]->Sprite()->GetPixel(x,y));
							}
						}
					}
				}
				SetDrawTarget(nullptr);
				attackingLineModified.Decal()->Update();
				//DrawPartialWarpedDecal()
				DrawCurvedTexture(vf2d{150-48,80}+setPieceOffset,{160,-30},attackingLineModified.Decal(),{arrowScroll,0},CONSTANT::ATTACKER_ATTACK_COL,0.8);
				{
					vf2d offset={0,20};
					DrawWarpedDecal(titleScreenText.Decal(),{vf2d{float(textOrientationX),float(textOrientationY)}+offset,vf2d{0,float(titleScreenText.Sprite()->height)}+offset,vf2d{float(titleScreenText.Sprite()->width),float(titleScreenText.Sprite()->height)}+offset,vf2d{float(titleScreenText.Sprite()->width-textOrientationX),float(textOrientationY)}+offset});
				}
				mainMenu.DrawDecal(this);
			}break;
		#pragma endregion
		#pragma region GAMEPLAY
			case GameState::GAMEPLAY:{
				HandleGUIDisplay();
				if(flags.playerInControl){
					HandleDraggingSelection();
					HandleRightClickMove();
					HandlePanAndZoom(fElapsedTime);
					HandleMinimapClick();
				}
				flashTimer+=fElapsedTime*2;
				if(flashTimer>1){
					flashTimer--;
				}
				SCENARIOS[currentScenario]->_Update();
				if(SCENARIOS[currentScenario]->transitionToNextLevel){
					if(SCENARIOS[currentScenario]->nextLevel!=FINISH){
						levelToLoad=SCENARIOS[currentScenario]->nextLevel;
					}else{
						state=GameState::COMPLETED;
					}
				}
				restartManager.Update(this);
				HandleRestartButton(fElapsedTime);
				PerformLevelTransition(fElapsedTime);
				AL.vecPos=gametv.ScreenToWorld(GetScreenSize()/2);
				AL.fSoundFXVolume=std::min(1.f,gametv.GetWorldScale().x);
				AL.OnUserUpdate(fElapsedTime);

				for(auto&tile:TileManager::visibleTiles){
					tile.second-=fElapsedTime;
				}

				std::erase_if(TileManager::visibleTiles,[](std::pair<vf2d,float> key){return key.second<=0;});

				CalculateUsedMemory();
				for(auto&u:units){
					u->SaveMemory();
					std::weak_ptr<Unit>closestUnit;
					float closestDist=999999;
					for(auto&u2:units){
						IdentifyClosestTarget(closestUnit,closestDist,u,u2);
						CollisionChecking(u,u2);
					}
					if(u->IsFriendly()){
						for(int y=-2;y<3;y++){
							for(int x=-2;x<3;x++){
								if(abs(x)+abs(y)<=2){
									TileManager::visibleTiles[u->GetPos()/24/4+vi2d(x,y)]=5;
								}
							}
						}
					}
					u->AttemptAttack(u,closestUnit,units,debuffIcons,IMAGES);
					u->_Update(this,SOUNDS,player_resources,enemy_resources,queuedUnits,resourceGainTimer,resourceGainIcons,IMAGES);
				}

				std::erase_if(units,[&](std::shared_ptr<Unit>u){
					if(u->GetHealth()==0){
						u->OnDeath(SOUNDS);
						deathAnimations.emplace_back(std::make_unique<DeathAnimation>(this,u->GetPos(),u->GetImage(),*IMAGES[MATRIX],u->IsFriendly()));
						return true;
					} else {
						return false;
					}
					});

				for(auto&queuedUnit:queuedUnits){
					units.push_back(std::move(queuedUnit));
				}

				queuedUnits.clear();

				DrawPartialDecal({0,0},GetScreenSize(),IMAGES[MATRIX]->Decal(),randomBackgroundOffset+gametv.GetWorldOffset()*(vf2d{32,32}/vf2d(GetScreenSize()))*gametv.GetWorldScale(),{32,32},Pixel{currentLevel->levelColor.r,currentLevel->levelColor.g,currentLevel->levelColor.b,164}/2);
				gametv.DrawPartialDecal({0,0},WORLD_SIZE*CONSTANT::TILE_SIZE,IMAGES[TILE]->Decal(),{0,0},WORLD_SIZE*CONSTANT::TILE_SIZE,currentLevel->levelColor);

				for(auto&u:units){
					u->DrawRangeIndicator(this,gametv,IMAGES);
				}

				for(auto&u:units){
					u->Draw(gametv,IMAGES);
					if(u->IsGuarded()){
						gametv.DrawDecal(u->GetPos()+vf2d{float(u->GetUnitSize().x/2),-float(u->GetUnitSize().y/2)}-vf2d{float(IMAGES[GUARD_ICON]->Sprite()->width),0.f}*0.375,IMAGES[GUARD_ICON]->Decal(),{0.375,0.375});
					}
				}
				for(auto&deadUnit:deathAnimations){
					deadUnit->Update(fElapsedTime);
					deadUnit->Draw(gametv,this);
				}

				std::erase_if(deathAnimations,[](auto&u){return u->IsDone();});

				for(auto&collectionPoint:collectionPoints){
					collectionPoint->Update(this,*IMAGES[MATRIX]);
					RenderCollectionPoints(collectionPoint.get());
				}

				for(auto&u:units){
					u->DrawUnitDamageStats(this,gametv,IMAGES);
				}

				for(DebuffIcon&icon:debuffIcons){
					icon.Update(fElapsedTime);
					icon.Draw(gametv);
				}

				for(ResourceGainIcon&icon:resourceGainIcons){
					icon.Update(fElapsedTime);
					icon.Draw(gametv);
				}

				std::erase_if(debuffIcons,[](DebuffIcon&icon){return icon.lifetime<=0;});
				std::erase_if(resourceGainIcons,[](ResourceGainIcon&icon){return icon.lifetime<=0;});

				for(auto&u:units){
					u->_DrawHud(gametv,IMAGES,flags.unitMetersGreyedOut);
				}

				DrawSelectionRectangle();
				RenderFogOfWar();

				unitCreationList.DrawDecal(this);
				platformCreationList.DrawDecal(this);
				restartManager.DrawDecal(this);

				DrawSystemMemoryBar(fElapsedTime);
				DrawResourceBar(fElapsedTime);
				if(flags.guideEnabled){
					DrawDecal({float(ScreenWidth()-74-IMAGES[GUIDE]->Sprite()->width*0.75),float(ScreenHeight()+6-IMAGES[GUIDE]->Sprite()->height*0.75)},IMAGES[GUIDE]->Decal(),{0.75,0.75});
				}
				DrawMinimap();

				unitCreationBox.UpdateAndDraw(GetMousePos()+vi2d{8,-28},this,player_resources,IMAGES,GetTotalUsedMemory(),currentLevel->availableMemory);
				testBox.UpdateAndDraw(GetMousePos()-testBox.GetSize()/2,this,player_resources,IMAGES,GetTotalUsedMemory(),currentLevel->availableMemory);
				memoryAllocatorBox.UpdateAndDraw(GetMousePos()+vi2d{8,-28},this,player_resources,IMAGES,GetTotalUsedMemory(),currentLevel->availableMemory);
				platformCreationBox.UpdateAndDraw(GetMousePos()+vi2d{8,-28},this,player_resources,IMAGES,GetTotalUsedMemory(),currentLevel->availableMemory);
				restartBox.UpdateAndDraw(GetMousePos()+vf2d{0,10},this,player_resources,IMAGES,GetTotalUsedMemory(),currentLevel->availableMemory);
				if(restartButtonHoldTime>0){
					FillRectDecal(restartButton->vPos+vf2d{3,(1-(restartButtonHoldTime/2.5f))*restartButton->vSize.y},{restartButton->vSize.x,((restartButtonHoldTime/2.5f))*restartButton->vSize.y},{255,255,255,128});
				}

				std::sort(units.begin(),units.end(),[&](auto&u1,auto&u2){
					float dist1=geom2d::line<float>(u1->GetGhostPos(),GetWorldMousePos()).length();
					float dist2=geom2d::line<float>(u2->GetGhostPos(),GetWorldMousePos()).length();
					return dist1>dist2;});

				SCENARIOS[currentScenario]->Draw();

				for(auto&u:units){
					if(u->IsGuarded()){
						bool changeOccured=false;
						int changedBit=-1;
						for(int i=0;i<u->memory.size();i++){
							if(u->memory[i]!=u->savedMemory[i]){
								changeOccured=true;
								changedBit=i;
								break;
							}
						}
						if(changeOccured&&util::random(1)<=0.3){
							u->memory[changedBit]=u->savedMemory[changedBit];
						}
					}
				}

				DrawPartialDecal({0,0},GetScreenSize(),IMAGES[MATRIX]->Decal(),randomBackgroundOffset+gametv.GetWorldOffset()*(vf2d{32,32}/vf2d(GetScreenSize()))*gametv.GetWorldScale(),{32,32},Pixel{currentLevel->levelColor.r,currentLevel->levelColor.g,currentLevel->levelColor.b,uint8_t(levelForegroundFade*255)}/2);
			}break;
		#pragma endregion
		#pragma region COMPLETED
			case GameState::COMPLETED:{
				DrawPartialDecal({0,0},GetScreenSize(),IMAGES[MATRIX]->Decal(),randomBackgroundOffset+gametv.GetWorldOffset()*(vf2d{32,32}/vf2d(GetScreenSize()))*gametv.GetWorldScale(),{32,32},Pixel{currentLevel->levelColor.r,currentLevel->levelColor.g,currentLevel->levelColor.b,164}/2);
			}break;
		#pragma endregion
		#pragma region CREDITS
			case GameState::CREDITS:{
				DrawPartialDecal({0,0},GetScreenSize(),IMAGES[MATRIX]->Decal(),randomBackgroundOffset+gametv.GetWorldOffset()*(vf2d{32,32}/vf2d(GetScreenSize()))*gametv.GetWorldScale(),{32,32},Pixel{currentLevel->levelColor.r,currentLevel->levelColor.g,currentLevel->levelColor.b,164}/2);
			}break;
		#pragma endregion
	}
	return true;
}

void VirusAttack::DrawSystemMemoryBar(float fElapsedTime){
	memoryChangeTimer=std::max(0.f,memoryChangeTimer-fElapsedTime);
	memoryDisplayDelay-=fElapsedTime;
	if(memoryDisplayDelay<=0){
		if(memoryDisplayAmt>lastTotalMemory){
			memoryDisplayAmt-=1;
		} else 
		if(memoryDisplayAmt<lastTotalMemory){
			memoryDisplayAmt+=1;
		}
		memoryDisplayDelay=0.01;
	}
	lastDisplayMemoryUpdateTimer-=fElapsedTime;
	if(lastDisplayMemoryUpdateTimer<=0){
		for(int i=0;i<playerUsedMemory.size();i++){
			if(playerUsedMemory[i]<playerUsedDisplayMemory[i]){
				playerUsedDisplayMemory[i]--;
			} else
			if(playerUsedMemory[i]>playerUsedDisplayMemory[i]){
				playerUsedDisplayMemory[i]++;
			}
		}
		for(int i=0;i<enemyUsedMemory.size();i++){
			if(enemyUsedMemory[i]<enemyUsedDisplayMemory[i]){
				enemyUsedDisplayMemory[i]--;
			} else
				if(enemyUsedMemory[i]>enemyUsedDisplayMemory[i]){
					enemyUsedDisplayMemory[i]++;
				}
		}
		lastDisplayMemoryUpdateTimer=0.01;
	}

	vf2d barPos={2,float(ScreenHeight()-7)};
	float barOffset=0;
	float barWidth=240;
	float actualBarWidth=barWidth+2;
	for(int i=0;i<playerUsedDisplayMemory.size();i++){
		float barSegmentWidth=(float(playerUsedDisplayMemory[i])/currentLevel->availableMemory)*actualBarWidth;
		Pixel col;
		switch(i){
			case 0:{
				col=CONSTANT::HEALTH_COLOR;
			}break;
			case 1:{
				col=CONSTANT::RANGE_COLOR;
			}break;
			case 2:{
				col=CONSTANT::ATKSPD_COLOR;
			}break;
			case 3:{
				col=CONSTANT::MOVESPD_COLOR;
			}break;
			case 4:{
				col=CONSTANT::PROCEDURE_COLOR;
			}break;
		}
		DrawPartialDecal(barPos+vf2d{barOffset+1,1.f},{barSegmentWidth,3},IMAGES[SEGMENT_BAR]->Decal(),{0,0},{float(playerUsedDisplayMemory[i]),3.f},col);
		barOffset+=barSegmentWidth;
	}
	FillRectDecal(barPos+vf2d{barOffset+1,1.f},{2,3},GREEN);
	barOffset=0;
	for(int i=0;i<enemyUsedDisplayMemory.size();i++){
		float barSegmentWidth=(float(enemyUsedDisplayMemory[i])/currentLevel->availableMemory)*actualBarWidth;
		Pixel col;
		switch(i){
		case 0:{
			col=CONSTANT::HEALTH_COLOR;
		}break;
		case 1:{
			col=CONSTANT::RANGE_COLOR;
		}break;
		case 2:{
			col=CONSTANT::ATKSPD_COLOR;
		}break;
		case 3:{
			col=CONSTANT::MOVESPD_COLOR;
		}break;
		case 4:{
			col=CONSTANT::PROCEDURE_COLOR;
		}break;
		}
		DrawPartialDecal(barPos+vf2d{barOffset+actualBarWidth+3-barSegmentWidth,1.f},{barSegmentWidth,3},IMAGES[SEGMENT_BAR]->Decal(),{0,0},{float(enemyUsedDisplayMemory[i]),3.f},col);
		barOffset-=barSegmentWidth;
	}
	FillRectDecal(barPos+vf2d{barOffset+actualBarWidth+3-2,1.f},{2,3},RED);

	DrawPartialDecal(barPos,{3,5},IMAGES[ROUND_BAR]->Decal(),{0,0},{3,5},flags.flashMemoryBar?PixelLerp(BLACK,CONSTANT::INCREASE_VALUE_COLOR,flashTimer):BLACK);
	for(int i=barPos.x+3;i<barWidth;i++){
		DrawPartialDecal(barPos+vf2d{3,0},{barWidth,5},IMAGES[ROUND_BAR]->Decal(),{2,0},{1,5},flags.flashMemoryBar?PixelLerp(BLACK,CONSTANT::INCREASE_VALUE_COLOR,flashTimer):BLACK);
	}
	DrawPartialDecal(barPos+vf2d{3+barWidth,0},{3,5},IMAGES[ROUND_BAR]->Decal(),{2,0},{3,5},flags.flashMemoryBar?PixelLerp(BLACK,CONSTANT::INCREASE_VALUE_COLOR,flashTimer):BLACK);
	vf2d textPos=barPos+vf2d{barWidth+6+4,0};
	if(GetTotalUsedMemory()>lastTotalMemory){
		memoryIncreased=true;
		memoryChangeTimer=2;
	} else 
	if(GetTotalUsedMemory()<lastTotalMemory){
		memoryIncreased=false;
		memoryChangeTimer=2;
	}
	lastTotalMemory=GetTotalUsedMemory();
	DrawShadowStringPropDecal(textPos,std::to_string(memoryDisplayAmt)+"/"+std::to_string(currentLevel->availableMemory)+"b",WHITE,PixelLerp(BLACK,memoryIncreased?CONSTANT::INCREASE_VALUE_COLOR:CONSTANT::DECREASE_VALUE_COLOR,memoryChangeTimer/2.0f),{0.6,0.6},0.6);
}

void VirusAttack::DrawResourceBar(float fElapsedTime){
	for(int i=0;i<resourceGainTimer.size();i++){
		resourceGainTimer[i]=std::max(0.f,resourceGainTimer[i]-fElapsedTime);
	}
	GradientFillRectDecal({0,0},{float(ScreenWidth()),12.f},BLACK,{VERY_DARK_BLUE.r,VERY_DARK_BLUE.g,VERY_DARK_BLUE.b,128},{VERY_DARK_BLUE.r,VERY_DARK_BLUE.g,VERY_DARK_BLUE.b,128},BLACK);
	DrawRectDecal({0,0},{float(ScreenWidth()),12.f},{3, 194, 252});
	auto DrawResourceDisplay=[&](int index,int resourceValue,int&previousResourceValue,int&displayResourceValue,Pixel col){
		resourceDisplayValueUpdateTimer[index]-=fElapsedTime;
		if(previousResourceValue<resourceValue){
			resourceIncreased[index]=true;
			resourceGainTimer[index]=2;
			previousResourceValue=resourceValue;
		} else
		if(previousResourceValue>resourceValue){
			resourceIncreased[index]=false;
			resourceGainTimer[index]=2;
			previousResourceValue=resourceValue;
		}
		if(resourceDisplayValueUpdateTimer[index]<=0){
			if(displayResourceValue<resourceValue){
				displayResourceValue++;
			} else
			if(displayResourceValue>resourceValue){
				displayResourceValue--;
			}
			resourceDisplayValueUpdateTimer[index]=0.01;
		}
		DrawDecal({6.f+index*42,1.f},IMAGES[RESOURCE]->Decal(),{1,1},col);
		DrawShadowStringDecal({6.f+index*42+20,2.f},std::to_string(displayResourceValue),col,PixelLerp(BLACK,resourceIncreased[index]?CONSTANT::INCREASE_VALUE_COLOR:CONSTANT::DECREASE_VALUE_COLOR,resourceGainTimer[index]/2.0f));
	};
	DrawResourceDisplay(0,player_resources.health,player_prev_resources.health,player_display_resources.health,CONSTANT::HEALTH_COLOR);
	DrawResourceDisplay(1,player_resources.atkSpd,player_prev_resources.atkSpd,player_display_resources.atkSpd,CONSTANT::ATKSPD_COLOR);
	DrawResourceDisplay(2,player_resources.moveSpd,player_prev_resources.moveSpd,player_display_resources.moveSpd,CONSTANT::MOVESPD_COLOR);
	DrawResourceDisplay(3,player_resources.range,player_prev_resources.range,player_display_resources.range,CONSTANT::RANGE_COLOR);
	DrawResourceDisplay(4,player_resources.procedure,player_prev_resources.procedure,player_display_resources.procedure,CONSTANT::PROCEDURE_COLOR);
}

void VirusAttack::RenderFogOfWar(){
	for(int y=gametv.GetTopLeftTile().y/96-1;y<=gametv.GetBottomRightTile().y/96+1;y++){
		for(int x=gametv.GetTopLeftTile().x/96-1;x<=gametv.GetBottomRightTile().x/96+1;x++){
			if(TileManager::visibleTiles.count(vi2d{x,y})==0){
				if(x>=0&&y>=0&&x<WORLD_SIZE.x/4&&y<WORLD_SIZE.y/4){
					gametv.FillRectDecal(vf2d{float(x),float(y)}*96,{96,96},{0,0,0,128});
				}
			}
		}
	}
}

bool VirusAttack::CanAfford(Resources&resources,std::vector<Memory>&unitCosts){
	int totalMemoryCost=0;
	for(Memory&mem:unitCosts){
		switch(mem.type){
			case HEALTH:{
				if(resources.health<mem.size)return false;
			}break;
			case ATKSPD:{
				if(resources.atkSpd<mem.size)return false;
			}break;
			case MOVESPD:{
				if(resources.moveSpd<mem.size)return false;
			}break;
			case RANGE:{
				if(resources.range<mem.size)return false;
			}break;
			case PROCEDURE:{
				if(resources.procedure<mem.size)return false;
			}break;
		}
		totalMemoryCost+=mem.size;
	}
	return totalMemoryCost+GetTotalUsedMemory()<=currentLevel->availableMemory;
}

void VirusAttack::ExpendResources(Resources&resources,std::vector<Memory>&unitCosts){
	for(Memory&mem:unitCosts){
		switch(mem.type){
			case HEALTH:{
				resources.health-=mem.size;
			}break;
			case ATKSPD:{
				resources.atkSpd-=mem.size;
			}break;
			case MOVESPD:{
				resources.moveSpd-=mem.size;
			}break;
			case RANGE:{
				resources.range-=mem.size;
			}break;
			case PROCEDURE:{
				resources.procedure-=mem.size;
			}break;
		}
	}
}

int VirusAttack::GetTotalUsedMemory(){
	return GetPlayerUsedMemory()+GetEnemyUsedMemory();
}
int VirusAttack::GetPlayerUsedMemory(){
	int sum=0;
	for(int i=0;i<5;i++){
		sum+=playerUsedMemory[i];
	}
	return sum;
}
int VirusAttack::GetEnemyUsedMemory(){
	int sum=0;
	for(int i=0;i<5;i++){
		sum+=enemyUsedMemory[i];
	}
	return sum;
}

void VirusAttack::CalculateUsedMemory(){
	playerUsedMemory=enemyUsedMemory={0};
	for(auto&u:units){
		std::array<size_t,5>costs={u->health.size,u->range.size,u->atkSpd.size,u->moveSpd.size,u->procedure.size};

		if(u->IsBuilding()){
			costs={u->GetBuildUnit()->health.size,u->GetBuildUnit()->range.size,u->GetBuildUnit()->atkSpd.size,u->GetBuildUnit()->moveSpd.size,u->GetBuildUnit()->procedure.size};
		}

		std::array<int,5>&targetArr=u->IsFriendly()?playerUsedMemory:enemyUsedMemory;
		for(int i=0;i<targetArr.size();i++){
			targetArr[i]+=costs[i];
		}
	}
}

bool VirusAttack::OnUserDestroy(){
	return true;
}

void VirusAttack::PerformLevelTransition(float fElapsedTime){
	if(levelToLoad!=currentLevel->name||reloadLevel){
		levelForegroundFade=std::min(1.f,levelForegroundFade+fElapsedTime);
		if(levelForegroundFade>=1){
			LoadLevel(levelToLoad);
			reloadLevel=false;
		}
	}else
	if(levelForegroundFade>0){
		levelForegroundFade=std::max(0.f,levelForegroundFade-fElapsedTime);
	}
}

void VirusAttack::RestartLevel(){
	reloadLevel=true;
}

void VirusAttack::HandleRestartButton(float fElapsedTime){
	if(restartButton->bHover){
		restartBox.Initialize("Click and hold to restart the level.",GetMousePos()+vf2d{0,10},"Restart Level",nullptr,{72,1});
		restartBox.SetVisible(true);
		if(restartButton->bPressed){
			restartButtonHeldDown=true;
		}
		if(restartButton->bReleased){
			restartBox.SetVisible(false);
			restartButtonHeldDown=false;
			restartButtonHoldTime=0;
		}
		if(restartButtonHeldDown){
			restartButtonHoldTime+=fElapsedTime;
			if(restartButtonHoldTime>=CONSTANT::RESTART_BUTTON_HOLD_TIME){
				RestartLevel();
				restartButtonHeldDown=false;
				restartButtonHoldTime=0;
			}
		}
	}else{
		restartBox.SetVisible(false);
		restartButtonHeldDown=false;
		restartButtonHoldTime=0;
	}
}

void VirusAttack::DrawCurvedTexture(vf2d offset,vf2d size,Decal*decal,vf2d texOffset,Pixel col,float curveThickness){
	vf2d curveMultiplier=size;
	vf2d curveDisplacement=offset;
	std::vector<vf2d>curvePoints={{0.f,abs(sin((0)*float(PI)))},{0.1,abs(sin((0.1f)*float(PI)))},{0.2,abs(sin((0.2f)*float(PI)))},{0.3,abs(sin((0.3f)*float(PI)))},{0.4,abs(sin((0.4f)*float(PI)))},{0.5,abs(sin((0.5f)*float(PI)))},{0.6,abs(sin((0.6f)*float(PI)))},{0.7,abs(sin((0.7f)*float(PI)))},{0.8,abs(sin((0.8f)*float(PI)))},{0.9,abs(sin((0.9f)*float(PI)))},{1.f,abs(sin((1.f)*float(PI)))}};
	vf2d curve2offset={0.f,curveThickness};
	std::vector<vf2d>curvePoints2={vf2d{0.f,abs(sin((0)*float(PI)))}+curve2offset,vf2d{0.1,abs(sin((0.1f)*float(PI)))}+curve2offset,vf2d{0.2,abs(sin((0.2f)*float(PI)))}+curve2offset,vf2d{0.3,abs(sin((0.3f)*float(PI)))}+curve2offset,vf2d{0.4,abs(sin((0.4f)*float(PI)))}+curve2offset,vf2d{0.5,abs(sin((0.5f)*float(PI)))}+curve2offset,vf2d{0.6f,abs(sin((0.6f)*float(PI)))}+curve2offset,vf2d{0.7,abs(sin((0.7f)*float(PI)))}+curve2offset,vf2d{0.8,abs(sin((0.8f)*float(PI)))}+curve2offset,vf2d{0.9,abs(sin((0.9f)*float(PI)))}+curve2offset,vf2d{1.f,abs(sin((1.f)*float(PI)))}+curve2offset};
	std::vector<vf2d>combinedCurvePoints={
		curvePoints2[0]*curveMultiplier+curveDisplacement,curvePoints[0]*curveMultiplier+curveDisplacement,
		curvePoints2[1]*curveMultiplier+curveDisplacement,curvePoints[1]*curveMultiplier+curveDisplacement,
		curvePoints2[2]*curveMultiplier+curveDisplacement,curvePoints[2]*curveMultiplier+curveDisplacement,
		curvePoints2[3]*curveMultiplier+curveDisplacement,curvePoints[3]*curveMultiplier+curveDisplacement,
		curvePoints2[4]*curveMultiplier+curveDisplacement,curvePoints[4]*curveMultiplier+curveDisplacement,
		curvePoints2[5]*curveMultiplier+curveDisplacement,curvePoints[5]*curveMultiplier+curveDisplacement,
		curvePoints2[6]*curveMultiplier+curveDisplacement,curvePoints[6]*curveMultiplier+curveDisplacement,
		curvePoints2[7]*curveMultiplier+curveDisplacement,curvePoints[7]*curveMultiplier+curveDisplacement,
		curvePoints2[8]*curveMultiplier+curveDisplacement,curvePoints[8]*curveMultiplier+curveDisplacement,
		curvePoints2[9]*curveMultiplier+curveDisplacement,curvePoints[9]*curveMultiplier+curveDisplacement,
		curvePoints2[10]*curveMultiplier+curveDisplacement,curvePoints[10]*curveMultiplier+curveDisplacement,
	};
	std::vector<vf2d>combinedUVPoints={
		{curvePoints[0].y+texOffset.x,1},{curvePoints[0].y+texOffset.x,0},
		{curvePoints[1].y+texOffset.x,1},{curvePoints[1].y+texOffset.x,0},
		{curvePoints[2].y+texOffset.x,1},{curvePoints[2].y+texOffset.x,0},
		{curvePoints[3].y+texOffset.x,1},{curvePoints[3].y+texOffset.x,0},
		{curvePoints[4].y+texOffset.x,1},{curvePoints[4].y+texOffset.x,0},
		{1-curvePoints[5].y+texOffset.x+1,1},{1-curvePoints[5].y+texOffset.x+1,0},
		{1-curvePoints[6].y+texOffset.x+1,1},{1-curvePoints[6].y+texOffset.x+1,0},
		{1-curvePoints[7].y+texOffset.x+1,1},{1-curvePoints[7].y+texOffset.x+1,0},
		{1-curvePoints[8].y+texOffset.x+1,1},{1-curvePoints[8].y+texOffset.x+1,0},
		{1-curvePoints[9].y+texOffset.x+1,1},{1-curvePoints[9].y+texOffset.x+1,0},
		{1-curvePoints[10].y+texOffset.x+1,1},{1-curvePoints[10].y+texOffset.x+1,0},
	};
	SetDecalStructure(DecalStructure::STRIP);
	DrawPolygonDecal(decal,combinedCurvePoints,combinedUVPoints,col);
	SetDecalStructure(DecalStructure::FAN);
}

void VirusAttack::HandleGUIDisplay(){
	bool allocatorSelected=false;
	bool platformSelected=false;
	bool memoryAllocatorBoxHovered=false;
	for(auto&u:units){
		u->UpdateGUIState(gametv,player_resources,memoryAllocatorBox,memoryAllocatorBoxHovered,GetTotalUsedMemory(),currentLevel->availableMemory);
		if(u->IsSelected()){
			if(u->IsAllocator()){
				allocatorSelected=true;
			} else
				if(u->IsPlatform()){
					platformSelected=true;
				}
		}
	}
	if(!memoryAllocatorBoxHovered)memoryAllocatorBox.SetVisible(false);
	UpdateUnitCreationListGUI(allocatorSelected);
	UpdatePlatformCreationListGUI(platformSelected);
}

int main()
{
	VirusAttack app;
	if (app.Construct(426, 320, 4, 4))
		app.Start();
	return 0;
}