You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
VirusAttack/olcCodeJam2023Entry/Unit.cpp

540 lines
14 KiB

#include "Unit.h"
#include "Constant.h"
#include "olcUTIL_Geometry2D.h"
#include "TileManager.h"
#include "util.h"
BasicUnit::BasicUnit(vf2d pos,std::map<Image,std::unique_ptr<Renderable>>&IMAGES,bool friendly,bool moveable)
:Unit({
{HEALTH,4},
{RANGE,2},
{ATKSPD,2},
{MOVESPD,3},
{PROCEDURE,1},
},pos,12,*IMAGES[VIRUS_IMG1],friendly,moveable){}
void BasicUnit::Attack(Unit&victim,std::vector<std::shared_ptr<Unit>>&otherUnits){
victim<<=1;
}
BasicUnit2::BasicUnit2(vf2d pos,std::map<Image,std::unique_ptr<Renderable>>&IMAGES,bool friendly,bool moveable)
:Unit({
{RANGE,2},
{ATKSPD,2},
{MOVESPD,3},
{PROCEDURE,1},
{HEALTH,4},
},pos,12,*IMAGES[VIRUS_IMG1],friendly,moveable){}
void BasicUnit2::Attack(Unit&victim,std::vector<std::shared_ptr<Unit>>&otherUnits){
victim>>=1;
}
LeftShifter::LeftShifter(vf2d pos,std::map<Image,std::unique_ptr<Renderable>>&IMAGES,bool friendly,bool moveable)
:Unit({
{RANGE,2},
{ATKSPD,2},
{MOVESPD,3},
{PROCEDURE,1},
{HEALTH,4},
},pos,12,*IMAGES[LEFT_SHIFTER],friendly,moveable){}
void LeftShifter::Attack(Unit&victim,std::vector<std::shared_ptr<Unit>>&otherUnits){
victim<<=1;
}
RightShifter::RightShifter(vf2d pos,std::map<Image,std::unique_ptr<Renderable>>&IMAGES,bool friendly,bool moveable)
:Unit({
{HEALTH,4},
{RANGE,2},
{ATKSPD,2},
{MOVESPD,3},
{PROCEDURE,1},
},pos,12,*IMAGES[RIGHT_SHIFTER],friendly,moveable){}
void RightShifter::Attack(Unit&victim,std::vector<std::shared_ptr<Unit>>&otherUnits){
victim>>=1;
}
BitRestorer::BitRestorer(vf2d pos,std::map<Image,std::unique_ptr<Renderable>>&IMAGES,bool friendly,bool moveable)
:Unit({
{PROCEDURE,6},
{RANGE,1},
{ATKSPD,1},
{MOVESPD,1},
{HEALTH,2},
},pos,12,*IMAGES[BIT_RESTORER],friendly,moveable,true,false){}
void BitRestorer::Attack(Unit&victim,std::vector<std::shared_ptr<Unit>>&otherUnits){
std::vector<int>emptyMemoryPositions;
for(int i=0;i<victim.GetMemorySize();i++){
if(!victim.memory[i]){
emptyMemoryPositions.emplace_back(i);
}
}
if(emptyMemoryPositions.size()==0){
//First see if we can find another damaged target, if we can, then we try healing them. Otherwise we exit.
AttemptToHealOtherAllies(otherUnits);
return;
}
int randomBit=emptyMemoryPositions[rand()%emptyMemoryPositions.size()];
victim.memory[randomBit]=victim.ghostMemory[randomBit]=true;
}
void BitRestorer::AttemptToHealOtherAllies(std::vector<std::shared_ptr<Unit>>&otherUnits){
std::vector<int>emptyMemoryPositions;
for(auto&u:otherUnits){
if(u.get()!=this&&u->IsFriendly()&&InRange(u)){
for(int i=0;i<u->GetMemorySize();i++){
if(!u->memory[i]){
emptyMemoryPositions.emplace_back(i);
}
}
if(emptyMemoryPositions.size()!=0){
int randomBit=emptyMemoryPositions[rand()%emptyMemoryPositions.size()];
u->memory[randomBit]=u->ghostMemory[randomBit]=true;
return;
}
}
}
}
MemorySwapper::MemorySwapper(vf2d pos,std::map<Image,std::unique_ptr<Renderable>>&IMAGES,bool friendly,bool moveable)
:Unit({
{RANGE,3},
{ATKSPD,1},
{HEALTH,3},
{PROCEDURE,3},
{MOVESPD,2},
},pos,12,*IMAGES[MEMORY_SWAPPER],friendly,moveable,true){}
void MemorySwapper::Attack(Unit&victim,std::vector<std::shared_ptr<Unit>>&otherUnits){
}
Corrupter::Corrupter(vf2d pos,std::map<Image,std::unique_ptr<Renderable>>&IMAGES,bool friendly,bool moveable)
:Unit({
{ATKSPD,3},
{RANGE,1},
{PROCEDURE,8},
{MOVESPD,4},
{HEALTH,4},
},pos,12,*IMAGES[CORRUPTER],friendly,moveable){}
void Corrupter::Attack(Unit&victim,std::vector<std::shared_ptr<Unit>>&otherUnits){
//Chooses a bit at random and corrupts it.
int randomBit=rand()%victim.memory.size();
victim.memory[randomBit]=victim.ghostMemory[randomBit]=false;
}
MemoryAllocator::MemoryAllocator(vf2d pos,std::map<Image,std::unique_ptr<Renderable>>&IMAGES,bool friendly,bool moveable)
:Unit({
{RANGE,1},
{ATKSPD,1},
{MOVESPD,1},
{PROCEDURE,1},
{HEALTH,1},
},pos,12,*IMAGES[UNIT_ALLOCATOR],friendly,true,false){}
void MemoryAllocator::Attack(Unit&victim,std::vector<std::shared_ptr<Unit>>&otherUnits){
}
RAMBank::RAMBank(PixelGameEngine*pge,vf2d pos,std::map<Image,std::unique_ptr<Renderable>>&IMAGES,bool friendly)
:Unit({
{RANGE,0},
{ATKSPD,0},
{MOVESPD,0},
{PROCEDURE,25},
{HEALTH,16},
},pos,41,*IMAGES[RAM_BANK],friendly,false
,false,false
),randomOffset({util::random(128),util::random(128)}),matrixImg(*IMAGES[MATRIX]),
originalImg(*IMAGES[RAM_BANK]){
img.Create(IMAGES[RAM_BANK]->Sprite()->width,IMAGES[RAM_BANK]->Sprite()->height);
pge->SetDrawTarget(img.Sprite());
pge->Clear(BLANK);
pge->DrawSprite({0,0},IMAGES[RAM_BANK]->Sprite());
pge->SetDrawTarget(nullptr);
}
void RAMBank::Attack(Unit&victim,std::vector<std::shared_ptr<Unit>>&otherUnits){
}
void RAMBank::Update(PixelGameEngine*pge){
pge->SetDrawTarget(img.Sprite());
for(int y=0;y<img.Sprite()->height;y++){
for(int x=0;x<img.Sprite()->width;x++){
Pixel col = originalImg.Sprite()->GetPixel(x,y);
if(col==WHITE){
pge->Draw(x,y,matrixImg.Sprite()->GetPixel(int(x+randomOffset.x),int(y+randomOffset.y)));
}
}
}
img.Decal()->Update();
pge->SetDrawTarget(nullptr);
}
void RAMBank::Draw(TileTransformedView&game,std::map<Image,std::unique_ptr<Renderable>>&IMAGES){
game.DrawRotatedDecal(GetGhostPos(),img.Decal(),0,img.Sprite()->Size()/2,{1,1},friendly?Pixel{192,192,255}:Pixel{255,192,192});
if(IsSelected()){
game.DrawRotatedDecal(GetGhostPos(),IMAGES[SELECTION_CIRCLE]->Decal(),0,IMAGES[SELECTION_CIRCLE]->Sprite()->Size()/2,vf2d(img.Sprite()->Size())/IMAGES[SELECTION_CIRCLE]->Sprite()->Size(),WHITE);
}
}
Unit::Unit(std::vector<Memory>memory,vf2d pos,float radius,Renderable&img,bool friendly,bool moveable,bool friendlyInteractable,bool enemyInteractable)
:pos(pos),radius(radius),ghostPos(pos),img(img),friendly(friendly),moveable(moveable),friendlyInteractable(friendlyInteractable),enemyInteractable(enemyInteractable){
int marker=0;
for(Memory&mem:memory){
for(int i=0;i<mem.size;i++){
this->memory.push_back(true);
this->ghostMemory.push_back(true);
}
switch(mem.type){
case HEALTH:{
health.index=marker;
health.size=mem.size;
}break;
case RANGE:{
range.index=marker;
range.size=mem.size;
}break;
case ATKSPD:{
atkSpd.index=marker;
atkSpd.size=mem.size;
}break;
case MOVESPD:{
moveSpd.index=marker;
moveSpd.size=mem.size;
}break;
case PROCEDURE:{
procedure.index=marker;
procedure.size=mem.size;
}break;
}
marker+=mem.size;
}
}
void Unit::DrawRangeIndicator(PixelGameEngine*pge,TileTransformedView&game,std::map<Image,std::unique_ptr<Renderable>>&IMAGES){
if(!CanInteractWithAllies()&&!CanInteractWithEnemies())return;
float dist=geom2d::line<float>(game.ScreenToWorld(pge->GetMousePos()),GetGhostPos()).length();
float range=12*(GetRange()+1);
float totalRange=GetUnitSize().x/2+range;
if(IsSelected()){
Pixel col;
if(CanInteractWithAllies()&&!CanInteractWithEnemies()){
col={40,127,173};
} else {
col={0,196,0};
}
game.DrawRotatedDecal(GetGhostPos(),IMAGES[RANGE_INDICATOR]->Decal(),0,IMAGES[RANGE_INDICATOR]->Sprite()->Size()/2,{totalRange/12,totalRange/12},col);
}else
if(dist<range*2){
Pixel col;
if(IsFriendly()){
if(CanInteractWithAllies()&&!CanInteractWithEnemies()){
col={40,127,173};
} else {
col={0,196,0};
}
}else{
if(CanInteractWithAllies()&&!CanInteractWithEnemies()){
col={194, 37, 168};
} else {
col={196,130,0};
}
}
game.DrawRotatedDecal(GetGhostPos(),IMAGES[RANGE_INDICATOR]->Decal(),0,IMAGES[RANGE_INDICATOR]->Sprite()->Size()/2,{totalRange/12,totalRange/12},{col.r,col.g,col.b,uint8_t((1.f-(dist/(range*2)))*255)});
}
}
void Unit::Draw(TileTransformedView&game,std::map<Image,std::unique_ptr<Renderable>>&IMAGES){
game.DrawRotatedDecal(ghostPos,img.Decal(),0,img.Sprite()->Size()/2,{1,1},friendly?Pixel{192,192,255}:Pixel{255,192,192});
if(IsSelected()){
game.DrawRotatedDecal(ghostPos,IMAGES[SELECTION_CIRCLE]->Decal(),0,IMAGES[SELECTION_CIRCLE]->Sprite()->Size()/2,vf2d(img.Sprite()->Size())/IMAGES[SELECTION_CIRCLE]->Sprite()->Size(),WHITE);
}
}
void Unit::DrawHud(TileTransformedView&game,std::map<Image,std::unique_ptr<Renderable>>&IMAGES){
int initialBarX=ghostPos.x-GetMemorySize()/2*CONSTANT::BAR_SQUARE_SIZE.x-CONSTANT::BAR_SQUARE_SIZE.x/2;
int initialBarY=ghostPos.y-CONSTANT::BAR_SQUARE_SIZE.y-img.Sprite()->height/2-2;
Pixel col=0;
auto CheckColor=[&](int i,Pixel&col){
if(health.index==i){
col=CONSTANT::HEALTH_COLOR;
}
if(range.index==i){
col=CONSTANT::RANGE_COLOR;
}
if(atkSpd.index==i){
col=CONSTANT::ATKSPD_COLOR;
}
if(moveSpd.index==i){
col=CONSTANT::MOVESPD_COLOR;
}
if(procedure.index==i){
col=CONSTANT::PROCEDURE_COLOR;
}
};
if(GetHealth()>0){
game.FillRectDecal(vf2d{float(initialBarX)+health.index*CONSTANT::BAR_SQUARE_SIZE.x,
float(initialBarY)}-vf2d{0,1},CONSTANT::BAR_SQUARE_SIZE+vf2d{CONSTANT::BAR_SQUARE_SIZE.x*health.size-1,2},CONSTANT::HEALTH_COLOR);
}
if(GetAtkSpd()>0){
game.FillRectDecal(vf2d{float(initialBarX)+atkSpd.index*CONSTANT::BAR_SQUARE_SIZE.x,
float(initialBarY)}-vf2d{0,1},CONSTANT::BAR_SQUARE_SIZE+vf2d{CONSTANT::BAR_SQUARE_SIZE.x*atkSpd.size-1,2},CONSTANT::ATKSPD_COLOR);
}
if(GetMoveSpd()>0){
game.FillRectDecal(vf2d{float(initialBarX)+moveSpd.index*CONSTANT::BAR_SQUARE_SIZE.x,
float(initialBarY)}-vf2d{0,1},CONSTANT::BAR_SQUARE_SIZE+vf2d{CONSTANT::BAR_SQUARE_SIZE.x*moveSpd.size-1,2},CONSTANT::MOVESPD_COLOR);
}
if(GetProcedure()>0){
game.FillRectDecal(vf2d{float(initialBarX)+procedure.index*CONSTANT::BAR_SQUARE_SIZE.x,
float(initialBarY)}-vf2d{0,1},CONSTANT::BAR_SQUARE_SIZE+vf2d{CONSTANT::BAR_SQUARE_SIZE.x*procedure.size-1,2},CONSTANT::PROCEDURE_COLOR);
}
if(GetRange()>0){
game.FillRectDecal(vf2d{float(initialBarX)+range.index*CONSTANT::BAR_SQUARE_SIZE.x,
float(initialBarY)}-vf2d{0,1},CONSTANT::BAR_SQUARE_SIZE+vf2d{CONSTANT::BAR_SQUARE_SIZE.x*range.size-1,2},CONSTANT::RANGE_COLOR);
}
for(int i=0;i<GetMemorySize();i++){
CheckColor(i,col);
game.FillRectDecal({float(initialBarX)+i*CONSTANT::BAR_SQUARE_SIZE.x,
float(initialBarY)},CONSTANT::BAR_SQUARE_SIZE,ghostMemory[i]?col:col/4);
game.DrawRectDecal({float(initialBarX)+i*CONSTANT::BAR_SQUARE_SIZE.x,
float(initialBarY)},CONSTANT::BAR_SQUARE_SIZE,BLACK);
}
}
int Unit::GetBits(Marker&m){
int activeBits=0;
for(int i=0;i<m.size;i++){
if(memory[i+m.index]){
activeBits++;
}
}
return activeBits;
}
int Unit::GetHealth(){
return GetBits(health);
}
int Unit::GetRange(){
return GetBits(range);
}
int Unit::GetAtkSpd(){
return GetBits(atkSpd);
}
int Unit::GetMoveSpd(){
return GetBits(moveSpd);
}
int Unit::GetProcedure(){
return GetBits(procedure);
}
int Unit::GetMemorySize(){
return memory.size();
}
void Unit::RunAI(PixelGameEngine*pge){}
void Unit::_RunAI(PixelGameEngine*pge){
RunAI(pge);
}
void Unit::_Update(PixelGameEngine*pge){
if(!target.expired()){
auto ptrTarget=target.lock();
if(!InRange(ptrTarget)){
SetPos(GetPos()+(ptrTarget->GetPos()-pos).norm()*GetMoveSpd()*24*pge->GetElapsedTime());
}
} else
if(targetLoc!=CONSTANT::UNSELECTED){
float dist=geom2d::line<float>(pos,targetLoc).length();
if(dist>24){
SetPos(GetPos()+(targetLoc-pos).norm()*GetMoveSpd()*24*pge->GetElapsedTime());
}
}
if(!IsFriendly()){
_RunAI(pge);
}
if(!GhostInFogOfWar()&&InFogOfWar()){
HideGhost();
}
reloadTimer=std::max(0.f,reloadTimer-pge->GetElapsedTime());
Update(pge);
}
std::vector<bool> operator <<(Unit&u,const int n){
std::vector<bool>tempMem=u.memory;
for(int i=0;i<u.GetMemorySize()-1;i++){
tempMem[i]=tempMem[i+1];
}
tempMem[u.GetMemorySize()-1]=0;
return tempMem;
}
std::vector<bool> operator >>(Unit&u,const int n){
std::vector<bool>tempMem=u.memory;
for(int i=1;i<u.GetMemorySize();i++){
tempMem[i]=tempMem[i-1];
}
tempMem[0]=0;
return tempMem;
}
bool Unit::IsFriendly(){
return friendly;
}
bool Unit::IsSelected(){
return selected;
}
void Unit::Select(){
selected=true;
}
void Unit::Deselect(){
selected=false;
}
vf2d Unit::GetPos(){
return pos;
}
bool Unit::IsDead(){
return dead;
}
vf2d Unit::GetUnitSize(){
return vf2d{radius,radius}*2;
}
void Unit::SetTargetUnit(std::weak_ptr<Unit>target){
this->target=target;
this->targetLoc=CONSTANT::UNSELECTED;
}
void Unit::SetTargetLocation(vf2d targetLoc){
this->target.reset();
this->targetLoc=targetLoc;
}
bool Unit::InRange(std::shared_ptr<Unit>target){
return InRange(target.get());
}
bool Unit::InRange(Unit*target){
float range=12*(GetRange()+1);
float totalRange=GetUnitSize().x/2+range;
return geom2d::overlaps(geom2d::circle<float>{GetPos(),totalRange},geom2d::circle<float>{target->GetPos(),target->GetUnitSize().x/2});
}
bool Unit::InRange(vf2d pos){
float range=12*(GetRange()+1);
float totalRange=GetUnitSize().x/2+range;
return geom2d::overlaps(geom2d::circle<float>{GetPos(),totalRange},geom2d::circle<float>{pos,0.1});
}
void Unit::SetPos(vf2d newPos){
pos=newPos;
if(!InFogOfWar()){
ghostPos=pos;
}
}
void Unit::AttemptAttack(std::weak_ptr<Unit>attacker,std::weak_ptr<Unit>unit,std::vector<std::shared_ptr<Unit>>&otherUnits){
if(reloadTimer>0)return;
std::weak_ptr<Unit>finalTarget;
if(!unit.expired()){
finalTarget=unit;
if(!target.expired()){
auto ptrTarget=target.lock();
if(InRange(ptrTarget)){
finalTarget=ptrTarget;
}
}
}
if(!finalTarget.expired()){
if(InRange(finalTarget.lock())){
_Attack(attacker,finalTarget,otherUnits); //Call the parent function first, followed by the child.
}
}
}
void Unit::Update(PixelGameEngine*pge){}
void Unit::Attacked(std::weak_ptr<Unit>attacker){}
void Unit::_Attacked(std::weak_ptr<Unit>attacker){
Attacked(attacker);
if(attacker.lock()->IsFriendly()!=IsFriendly()&&CanInteractWithEnemies()){
SetTargetUnit(attacker);
}
}
void Unit::_Attack(std::weak_ptr<Unit>attacker,std::weak_ptr<Unit>finalTarget,std::vector<std::shared_ptr<Unit>>&otherUnits){
if(GetAtkSpd()>0){
Attack(*finalTarget.lock(),otherUnits);
finalTarget.lock()->_Attacked(attacker);
reloadTimer=1.f/(GetAtkSpd()/2.f);
if(GetCurrentTarget().expired()&&!IsFriendly()){
if(finalTarget.lock()->IsFriendly()!=IsFriendly()&&CanInteractWithEnemies()){
SetTargetUnit(finalTarget);
}
}
}
}
bool Unit::InFogOfWar(){
return TileManager::visibleTiles.count(GetPos()/96)==0;
}
bool Unit::GhostInFogOfWar(){
return TileManager::visibleTiles.count(ghostPos/96)==0;
}
void Unit::HideGhost(){
ghostPos={99999,-99999};
}
vf2d Unit::GetGhostPos(){
return ghostPos;
}
bool Unit::IsMoveable(){
return moveable;
}
bool Unit::CanInteractWithAllies(){
return friendlyInteractable;
}
bool Unit::CanInteractWithEnemies(){
return enemyInteractable;
}
Renderable&Unit::GetImage(){
return img;
}
std::weak_ptr<Unit>Unit::GetCurrentTarget(){
return target;
}