Merge main into nations-ai

This commit is contained in:
Scott Anderson
2025-04-18 01:38:14 -04:00
40 changed files with 1899 additions and 422 deletions
+12 -12
View File
@@ -31,6 +31,7 @@ REGION=$1
VERSION_TAG="latest"
DOCKER_REPO=""
ENV=""
SSH_KEY=""
# Set environment-specific variables
if [ "$REGION" == "staging" ]; then
@@ -38,15 +39,18 @@ if [ "$REGION" == "staging" ]; then
SERVER_HOST=$SERVER_HOST_STAGING
DOCKER_REPO=$DOCKER_REPO_STAGING
ENV="staging"
SSH_KEY=$SSH_KEY_STAGING
elif [ "$REGION" == "us" ]; then
print_header "DEPLOYING TO US ENVIRONMENT"
SERVER_HOST=$SERVER_HOST_US
DOCKER_REPO=$DOCKER_REPO_PROD # Uses prod Docker repo for alt environment
SSH_KEY=$SSH_KEY_PROD
ENV="prod"
else
print_header "DEPLOYING TO EU ENVIRONMENT"
SERVER_HOST=$SERVER_HOST_EU
DOCKER_REPO=$DOCKER_REPO_PROD
SSH_KEY=$SSH_KEY_PROD
ENV="prod"
fi
@@ -57,10 +61,11 @@ if [ -z "$SERVER_HOST" ]; then
fi
# Configuration
SSH_KEY=${SSH_KEY:-"~/.ssh/id_rsa"} # Use default or override from .env
DOCKER_USERNAME=${DOCKER_USERNAME} # Docker Hub username
UPDATE_SCRIPT="./update.sh" # Path to your update script
REMOTE_UPDATE_SCRIPT="/root/update-openfront.sh" # Where to place the script on server
REMOTE_USER="openfront"
REMOTE_UPDATE_PATH="/home/$REMOTE_USER"
REMOTE_UPDATE_SCRIPT="$REMOTE_UPDATE_PATH/update-openfront.sh" # Where to place the script on server
# Check if update script exists
if [ ! -f "$UPDATE_SCRIPT" ]; then
@@ -90,28 +95,23 @@ if [ $? -ne 0 ]; then
exit 1
fi
if [ $? -ne 0 ]; then
echo "❌ Failed to push image to Docker Hub. Stopping deployment."
exit 1
fi
echo "✅ Docker image built and pushed successfully."
# Step 2: Copy update script to Hetzner server
print_header "STEP 2: Copying update script to server"
echo "Target: $SERVER_HOST"
echo "Target: $REMOTE_USER@$SERVER_HOST"
# Make sure the update script is executable
chmod +x $UPDATE_SCRIPT
# Copy the update script to the server
scp -i $SSH_KEY $UPDATE_SCRIPT $SERVER_HOST:$REMOTE_UPDATE_SCRIPT
scp -i $SSH_KEY $UPDATE_SCRIPT $REMOTE_USER@$SERVER_HOST:$REMOTE_UPDATE_SCRIPT
# Copy environment variables if needed
if [ -f .env ]; then
scp -i $SSH_KEY .env $SERVER_HOST:/root/.env
scp -i $SSH_KEY .env $REMOTE_USER@$SERVER_HOST:$REMOTE_UPDATE_PATH/.env
# Secure the .env file
ssh -i $SSH_KEY $SERVER_HOST "chmod 600 /root/.env"
ssh -i $SSH_KEY $REMOTE_USER@$SERVER_HOST "chmod 600 $REMOTE_UPDATE_PATH/.env"
fi
if [ $? -ne 0 ]; then
@@ -125,7 +125,7 @@ echo "✅ Update script successfully copied to server."
print_header "STEP 3: Executing update script on server"
# Make the script executable on the remote server and execute it with the environment parameter
ssh -i $SSH_KEY $SERVER_HOST "chmod +x $REMOTE_UPDATE_SCRIPT && $REMOTE_UPDATE_SCRIPT $REGION $DOCKER_USERNAME $DOCKER_REPO"
ssh -i $SSH_KEY $REMOTE_USER@$SERVER_HOST "chmod +x $REMOTE_UPDATE_SCRIPT && $REMOTE_UPDATE_SCRIPT $REGION $DOCKER_USERNAME $DOCKER_REPO"
if [ $? -ne 0 ]; then
echo "❌ Failed to execute update script on server."
+18 -12
View File
@@ -1,14 +1,20 @@
# AWS Configuration
AWS_REGION=region-name
AWS_ACCOUNT_ID=your-account-id
# Server Configuration
SERVER_HOST_STAGING=xxx.xxx.xx.xxx
SERVER_HOST_EU=xxx.xxx.xxx.xxx
SERVER_HOST_US=x.xxx.xxx.xxx
SSH_KEY_STAGING=~/.ssh/your-staging-key
SSH_KEY_PROD=~/.ssh/your-prod-key
# ECR (Elastic Container Registry)
ECR_REPO_NAME=your-repo-name
# Docker Configuration
DOCKER_USERNAME=username
DOCKER_REPO_PROD=your-prod-repo
DOCKER_REPO_STAGING=your-staging-repo
DOCKER_TOKEN=your_docker_token
# EC2 Deployment Hosts
EC2_HOST_STAGING=ec2-user@your-staging-ip
EC2_HOST_PROD=ec2-user@your-production-ip
EC2_KEY=~/.ssh/your-key-file.pem
# Application Secrets
ADMIN_TOKEN=your-admin-token
# Admin credentials
ADMIN_TOKEN=your_admin_token
R2_ACCESS_KEY=your_r2_access_key
R2_SECRET_KEY=your_r2_secret_key
R2_ACCOUNT_ID=your_r2_account_id
R2_PROD_BUCKET=your-prod-bucket
R2_STAGING_BUCKET=your-staging-bucket
+834 -2
View File
@@ -1,5 +1,5 @@
ownerdomain=openfront.io
managerdomain=venatus.com
managerdomain=adinplay.com
#V 01.04.2025 VH
#V
@@ -164,4 +164,836 @@ criteo.com, B-068503, DIRECT
appnexus.com, 806, DIRECT, f5ab79cb980f11d1
appnexus.com,1908,RESELLER,f5ab79cb980f11d1
adinplay.com, FTB, DIRECT
venatus.com, 67f90df66f43edab7e84d165, DIRECT
venatus.com, 67f90df66f43edab7e84d165, DIRECT
##################################
# AdinPlay.com ads.txt - 2025-04-16
##################################
adinplay.com, OFI, DIRECT
#Google
google.com, pub-3282547114800347, RESELLER, f08c47fec0942fa0
#Appnexus
appnexus.com, 8631, RESELLER, f5ab79cb980f11d1
#Index
indexexchange.com, 186547, RESELLER, 50b1c356f2c5c8fc
indexexchange.com, 187218, RESELLER, 50b1c356f2c5c8fc
indexexchange.com, 177754, RESELLER, 50b1c356f2c5c8fc
indexexchange.com, 196862, RESELLER, 50b1c356f2c5c8fc
indexexchange.com, 207014, RESELLER, 50b1c356f2c5c8fc
#Pulsepoint
contextweb.com, 561767, RESELLER, 89ff185a4c4e857c
#Pubmatic
pubmatic.com, 156975, RESELLER, 5d62403b186f2ace
pubmatic.com, 156857, RESELLER, 5d62403b186f2ace
pubmatic.com, 162231, RESELLER, 5d62403b186f2ace
#OpenX
openx.com, 540164985, RESELLER, 6a698e2ec38604c6
openx.com, 540010967, RESELLER, 6a698e2ec38604c6
openx.com, 540182293, RESELLER, 6a698e2ec38604c6
openx.com, 556894440, RESELLER, 6a698e2ec38604c6
#Sovrn
sovrn.com, 268781, RESELLER, fafdf38b16bf6b2b
lijit.com, 268781, RESELLER, fafdf38b16bf6b2b
lijit.com, 268781-eb, DIRECT, fafdf38b16bf6b2b
appnexus.com, 1360, RESELLER, f5ab79cb980f11d1
openx.com, 538959099, RESELLER, 6a698e2ec38604c6
openx.com, 539924617, RESELLER, 6a698e2ec38604c6
pubmatic.com, 137711, RESELLER, 5d62403b186f2ace
pubmatic.com, 156212, RESELLER, 5d62403b186f2ace
rubiconproject.com, 17960, RESELLER, 0bfd66d529a55807
sovrn.com, 264160, RESELLER, fafdf38b16bf6b2b
lijit.com, 264160, RESELLER, fafdf38b16bf6b2b
lijit.com, 264160-eb, DIRECT, fafdf38b16bf6b2b
smartadserver.com, 4125, RESELLER
sharethrough.com,7144eb80,RESELLER
#Oath
coxmt.com, 2000067907202, RESELLER
pubmatic.com, 156377, RESELLER, 5d62403b186f2ace #banner
pubmatic.com, 156078, RESELLER, 5d62403b186f2ace #banner
pubmatic.com, 155967, RESELLER, 5d62403b186f2ace #banner
openx.com, 537143344, RESELLER, 6a698e2ec38604c6
indexexchange.com, 175407, RESELLER, 50b1c356f2c5c8fc
#Rhythmone
rhythmone.com, 1432377581,DIRECT, a670c89d4a324e47
rhythmone.com, 665259327, DIRECT, a670c89d4a324e47
rhythmone.com, 2451244104, RESELLER, a670c89d4a324e47
video.unrulymedia.com, 2451244104, RESELLER
video.unrulymedia.com, 1432377581, DIRECT
#Gumgum
aolcloud.net,9904,RESELLER
appnexus.com,1001,DIRECT,f5ab79cb980f11d1
appnexus.com,2758,RESELLER,f5ab79cb980f11d1
appnexus.com,3135,DIRECT,f5ab79cb980f11d1
bidtellect.com,1407,RESELLER,1c34aa2d85d45e93
contextweb.com,558355,RESELLER,89ff185a4c4e857c
openx.com,537120563,DIRECT,6a698e2ec38604c6
openx.com,537149485,RESELLER,6a698e2ec38604c6
google.com,pub-9557089510405422,DIRECT,f08c47fec0942fa0
google.com,pub-3848273848634341,RESELLER,f08c47fec0942fa0
google.com, pub-7861278482560604, RESELLER, f08c47fec0942fa0
rhythmone.com,78519861,RESELLER, a670c89d4a324e47
outbrain.com,01a755b08c8c22b15d46a8b753ab6955d4,RESELLER
appnexus.com,7597,RESELLER,f5ab79cb980f11d1
openx.com,540003333,RESELLER,6a698e2ec38604c6
33across.com,0013300001r0t9mAAA,RESELLER
#Amazon
aps.amazon.com,53b902f9-cf9c-4605-aec3-2c8ce65042b8,DIRECT
gumgum.com,13543,DIRECT,ffdef49475d318a9
appnexus.com,8631,DIRECT,f5ab79cb980f11d1
indexexchange.com,196862,DIRECT,50b1c356f2c5c8fc
pubmatic.com,160006,RESELLER,5d62403b186f2ace
pubmatic.com,160096,RESELLER,5d62403b186f2ace
rubiconproject.com,18020,RESELLER,0bfd66d529a55807
pubmatic.com,162231,DIRECT,5d62403b186f2ace
appnexus.com,1908,RESELLER,f5ab79cb980f11d1
smaato.com,1100044650,RESELLER,07bcf65f187117b4
ad-generation.jp,12474,RESELLER,7f4ea9029ac04e53
districtm.io,100962,RESELLER,3fd707be9c4527c3
yieldmo.com,2719019867620450718,RESELLER
appnexus.com,3663,RESELLER,f5ab79cb980f11d1
rhythmone.com,1654642120,RESELLER,a670c89d4a324e47
yahoo.com,55029,RESELLER,e1a5b5b6e3255540
gumgum.com,14141,RESELLER,ffdef49475d318a9
admanmedia.com,726,RESELLER
emxdgt.com,2009,RESELLER,1e1d41537f7cad7f
appnexus.com,1356,RESELLER,f5ab79cb980f11d1
contextweb.com,562541,RESELLER,89ff185a4c4e857c
themediagrid.com,JTQKMP,RESELLER,35d5010d7789b49d
sovrn.com,375328,RESELLER,fafdf38b16bf6b2b
lijit.com,375328,RESELLER,fafdf38b16bf6b2b
beachfront.com,14804,RESELLER,e2541279e8e2ca4d
improvedigital.com,2050,RESELLER
mintegral.com,10043,RESELLER,0aeed750c80d6423
sonobi.com,7f5fa520f8,RESELLER,d1a215d9eb5aee9e
openx.com,556894440,DIRECT,6a698e2ec38604c6
onetag.com,7683ebe7bee7969,DIRECT
media.net,8CUZ1MK22,RESELLER
sharethrough.com,buaxQzOE,DIRECT,d53b998a7bd4ecd2
smartadserver.com,4571,DIRECT,060d053dcf45cbf3
mediago.io,045ac24b888bcf59a09731e7f0f2084f,RESELLER
adyoulike.com,7463c359225e043c111036d7a29affa5,RESELLER
minutemedia.com,01gya4708ddm,RESELLER
visiblemeasures.com,1052,RESELLER
undertone.com,4205,RESELLER,d954590d0cb265b9
admedia.com,AM1601,RESELLER,ae6c32151e71f19d
triplelift.com,8472,DIRECT,6c33edb13117fd86
kargo.com,8824,RESELLER
start.io,123111883,RESELLER
connectad.io,455,RESELLER,85ac85a30c93b3e5
# 33Across
rubiconproject.com, 16414, RESELLER, 0bfd66d529a55807 #33Across #hb #tag
rubiconproject.com, 21642, RESELLER, 0bfd66d529a55807 #33Across #hb #tag #viewable
rubiconproject.com, 21434, RESELLER, 0bfd66d529a55807 #33Across #tag #ebda
rubiconproject.com, 21720, RESELLER, 0bfd66d529a55807 #33Across EU #hb #tag
pubmatic.com, 156423, RESELLER, 5d62403b186f2ace #33Across #hb #tag
pubmatic.com, 158136, RESELLER, 5d62403b186f2ace #33Across EU #hb #tag
pubmatic.com, 158569, RESELLER, 5d62403b186f2ace #33Across #tag #ebda
appnexus.com, 10239, RESELLER, f5ab79cb980f11d1 #33Across #hb #tag #viewable
appnexus.com, 1001, RESELLER, f5ab79cb980f11d1 #33Across #tag
appnexus.com, 3135, RESELLER, f5ab79cb980f11d1 #33Across #tag
openx.com, 537120563, RESELLER, 6a698e2ec38604c6 #33Across #hb #tag
openx.com, 539392223, RESELLER, 6a698e2ec38604c6 #33Across #tag #ebda
openx.com, 540995201, RESELLER, 6a698e2ec38604c6 #33Across #hb #tag #viewable
adtech.com, 12094, RESELLER #33Across #hb #tag
adtech.com, 9993, RESELLER #33Across #tag
aol.com, 47594, RESELLER, e1a5b5b6e3255540 #33Across #hb #tag #viewable
yahoo.com, 55188, DIRECT, e1a5b5b6e3255540 #33Across #tag #ebda
advangelists.com, 8d3bba7425e7c98c50f52ca1b52d3735, RESELLER, 60d26397ec060f98 #33Across #hb #tag
sonobi.com, a416546bb7, RESELLER, d1a215d9eb5aee9e #33Across #tag #ebda
indexexchange.com, 190966, RESELLER, 50b1c356f2c5c8fc #33Across #tag #ebda
indexexchange.com, 183635, RESELLER, 50b1c356f2c5c8fc #33Across #hb #tag #viewable
google.com, pub-9557089510405422, RESELLER, f08c47fec0942fa0 #33Across #tag
#Rubiconproject
rubiconproject.com, 15636, RESELLER, 0bfd66d529a55807
#LockerDome
lockerdome.com, 11908041977355520, DIRECT
#Yield Nexus
yieldnexus.com, 1, DIRECT
ssp.ynxs.io, 185, DIRECT
appnexus.com, 10617, RESELLER, f5ab79cb980f11d1
appnexus.com, 9393, RESELLER, f5ab79cb980f11d1
advertising.com, 25034, RESELLER
sonobi.com, 783272317b, RESELLER, d1a215d9eb5aee9e
indexexchange.com, 186684,RESELLER, 50b1c356f2c5c8fc
#CPM
appnexus.com, 9624, RESELLER, f5ab79cb980f11d1
adtech.com, 11506, RESELLER
yahoo.com, 56896, RESELLER
pubmatic.com, 156078, RESELLER, 5d62403b186f2ace
advertising.com, 25218, RESELLER #video, US
beachfront.com, 9065, RESELLER
contextweb.com, 559969, RESELLER, 89ff185a4c4e857c
indexexchange.com, 189455, RESELLER, 50b1c356f2c5c8fc
advertising.com, 28320, RESELLER
richaudience.com, NtMZGaQQTT, RESELLER
adform.com, 1942, RESELLER
adform.com, 1941, RESELLER
adtech.com, 4687, RESELLER
aerserv.com, 2750, RESELLER, 2ce496b9f80eb9fa
aol.com, 27093, RESELLER
aol.com, 46658, RESELLER
aolcloud.net, 4687, RESELLER
appnexus.com, 2928, RESELLER, f5ab79cb980f11d1
contextweb.com, 560520, RESELLER, 89ff185a4c4e857c
google.com, pub-9115524111147081, RESELLER, f08c47fec0942fa0
google.com, pub-4673227357197067, RESELLER, f08c47fec0942fa0
indexexchange.com, 179394, RESELLER, 50b1c356f2c5c8fc
lijit.com, 249425, RESELLER, fafdf38b16bf6b2b
cpmstar.com, 49818, RESELLER
mobfox.com, 74240, RESELLER
mobfox.com, 45499, RESELLER
openx.com, 539625136, RESELLER, 6a698e2ec38604c6
smaato.com, 1100037086, RESELLER
smaato.com, 1100000579, RESELLER
sovrn.com, 249425, RESELLER, fafdf38b16bf6b2b
openx.com, 541079309, RESELLER, 6a698e2ec38604c6
openx.com, 541166421, RESELLER, 6a698e2ec38604c6
contextweb.com, 562263, RESELLER, 89ff185a4c4e857c
districtm.io, 102015, RESELLER, 3fd707be9c4527c3
lkqd.net, 304, RESELLER, 59c49fa9598a0117
lkqd.com, 304, RESELLER, 59c49fa9598a0117
advertising.com, 2694, RESELLER
google.com, pub-5781531207509232, RESELLER, f08c47fec0942fa0
appnexus.com, 806, RESELLER, f5ab79cb980f11d1
freewheel.tv, 211121, RESELLER
freewheel.tv, 211129, RESELLER
indexexchange.com, 183921, RESELLER, 50b1c356f2c5c8fc
openx.com, 540134228, RESELLER, 6a698e2ec38604c6
openx.com, 540634629, RESELLER, 6a698e2ec38604c6
pubmatic.com, 156715, RESELLER, 5d62403b186f2ace
rubiconproject.com, 13762, RESELLER, 0bfd66d529a55807
smartadserver.com, 3490, RESELLER
springserve.com, 550, RESELLER, a24eb641fc82e93d
beachfront.com, 4969, RESELLER, e2541279e8e2ca4d
advertising.com, 26282, RESELLER
pubmatic.com, 157310, RESELLER, 5d62403b186f2ace
rhythmone.com, 2968119028, RESELLER, a670c89d4a324e47
contextweb.com, 561910, RESELLER, 89ff185a4c4e857c
openx.com, 540226160, RESELLER, 6a698e2ec38604c6
openx.com, 540255318, RESELLER, 6a698e2ec38604c6
ssp.ynxs.io, 185, RESELLER
tremorhub.com, hpwve, RESELLER, 1a4e959a1b50034a
telaria.com, hpwve, RESELLER, 1a4e959a1b50034a
video.unrulymedia.com, UNRX-PUB-29dad46b-9bec-43c7-b950-c59d09cc8c71, RESELLER
video.unrulymedia.com, 985572675, RESELLER
rhythmone.com, 2864567592, RESELLER, a670c89d4a324e47
vidoomy.com, 51019, RESELLER
aol.com, 22762, RESELLER
freewheel.tv, 872257, RESELLER
openx.com, 540804929, RESELLER, 6a698e2ec38604c6
emxdgt.com, 1495, RESELLER, 1e1d41537f7cad7f
#Rubicon
rubiconproject.com, 23042, RESELLER, 0bfd66d529a55807
rubiconproject.com, 23044, RESELLER, 0bfd66d529a55807
#AMX
amxrtb.com, 105199469, RESELLER
appnexus.com, 12290, RESELLER, f5ab79cb980f11d1
appnexus.com, 11786, RESELLER, f5ab79cb980f11d1
indexexchange.com, 191503, RESELLER, 50b1c356f2c5c8fc
lijit.com, 260380, RESELLER, fafdf38b16bf6b2b
sovrn.com, 260380, RESELLER, fafdf38b16bf6b2b
pubmatic.com, 158355, RESELLER, 5d62403b186f2ace
appnexus.com, 9393, RESELLER, f5ab79cb980f11d1 #Video #Display
appnexus.com, 11924, RESELLER, f5ab79cb980f11d1
#Kueez
kueez.com, fe46d13305ce1b89f18a84c52275b7fe, DIRECT
appnexus.com, 8826, RESELLER
rubiconproject.com, 16920, RESELLER
openx.com, 557564833, RESELLER
lijit.com, 407406, RESELLER
media.net, 8cu4jtrf9, RESELLER
pubmatic.com, 162110, RESELLER
sharethrough.com, n98xdzel, RESELLER
33across.com, 0010b00002odu4haax, RESELLER
yieldmo.com, 3133660606033240149, RESELLER
onetag.com, 6e053d779444c00, RESELLER
video.unrulymedia.com, 3486482593, RESELLER
sonobi.com, 4c4fba1717, RESELLER
smartadserver.com, 4288, RESELLER
zetaglobal.com, 108, RESELLER
improvedigital.com, 2106, RESELLER
loopme.com, 11576, RESELLER
themediagrid.com, uot45z, RESELLER
#Aniview
aniview.com, 606c5af8b82e996ca965f498, RESELLER, 78b21b97965ec3f8
advertising.com, 23089, RESELLER
appnexus.com, 12637, RESELLER, f5ab79cb980f11d1
appnexus.com, 9382, RESELLER, f5ab79cb980f11d1
synacor.com, 82171, RESELLER, e108f11b2cdf7d5b
pubmatic.com, 156344, RESELLER, 5d62403b186f2ace
rubiconproject.com, 13344, RESELLER, 0bfd66d529a55807
indexexchange.com, 191740, RESELLER, 50b1c356f2c5c8fc
conversantmedia.com, 100195, DIRECT, 03113cd04947736d
appnexus.com, 4052, RESELLER, f5ab79cb980f11d1
contextweb.com, 561998, RESELLER, 89ff185a4c4e857c
pubmatic.com, 158100, RESELLER, 5d62403b186f2ace
yahoo.com, 55771, RESELLER, e1a5b5b6e3255540
onetag.com, 57e618150c70d90, DIRECT
google.com, pub-3769010358500643, RESELLER, f08c47fec0942fa0
video.unrulymedia.com, 3350674472, DIRECT
rhythmone.com, 3350674472, DIRECT, a670c89d4a324e47
google.com, pub-4586415728471297, RESELLER, f08c47fec0942fa0
google.com, pub-3565385483761681, DIRECT, f08c47fec0942fa0
google.com, pub-5717092533913515, RESELLER, f08c47fec0942fa0
smartadserver.com, 2786, DIRECT
improvedigital.com, 1147, DIRECT
google.com, pub-2930805104418204, RESELLER, f08c47fec0942fa0
google.com, pub-4903453974745530, RESELLER, f08c47fec0942fa0
richaudience.com, 1ru8dKmJJV, DIRECT
advertising.com, 7574, RESELLER
appnexus.com, 8233, RESELLER, f5ab79cb980f11d1
pubmatic.com, 81564, RESELLER, 5d62403b186f2ace
pubmatic.com, 156538, RESELLER, 5d62403b186f2ace
rubiconproject.com, 13510, RESELLER, 0bfd66d529a55807
smartadserver.com, 2640, RESELLER
smartadserver.com, 2441, RESELLER
yahoo.com, 57857, RESELLER, e1a5b5b6e3255540
undertone.com, 4077, DIRECT
appnexus.com, 2234, RESELLER, f5ab79cb980f11d1
rubiconproject.com, 22412, RESELLER, 0bfd66d529a55807
advertising.com, 28650, RESELLER
pubmatic.com, 160318, RESELLER, 5d62403b186f2ace
pubmatic.com, 160319, RESELLER, 5d62403b186f2ace
appnexus.com, 10112, RESELLER, f5ab79cb980f11d1
google.com, pub-0679975395820445, RESELLER, f08c47fec0942fa0
google.com, pub-9936969251765866, RESELLER, f08c47fec0942fa0
#Fluct
adingo.jp, 25262, RESELLER
pubmatic.com, 156313, RESELLER, 5d62403b186f2ace
appnexus.com, 7044, RESELLER, f5ab79cb980f11d1
pubmatic.com, 158060, RESELLER, 5d62403b186f2ace
#Conversant
conversantmedia.com, 100106, RESELLER, 03113cd04947736d
lijit.com, 411121, RESELLER, fafdf38b16bf6b2b #SOVRN
admanmedia.com, 2050, RESELLER
Appnerve.com, 187287, RESELLER
rubiconproject.com, 23644, RESELLER, 0bfd66d529a55807
#OneTag
onetag.com, 7683ebe7bee7969, RESELLER
onetag.com, 7683ebe7bee7969-OB, RESELLER
appnexus.com, 13099, RESELLER, f5ab79cb980f11d1
yahoo.com, 58905, RESELLER, e1a5b5b6e3255540
rubiconproject.com, 11006, RESELLER, 0bfd66d529a55807
smartadserver.com, 4111, RESELLER
#Media.net
media.net, 8CUEHU9Y5, RESELLER
openx.com, 537100188, RESELLER, 6a698e2ec38604c6
pubmatic.com, 159463, RESELLER, 5d62403b186f2ace
emxdgt.com, 1759, RESELLER, 1e1d41537f7cad7f
google.com, pub-7439041255533808, RESELLER, f08c47fec0942fa0
rubiconproject.com, 19396, RESELLER, 0bfd66d529a55807
onetag.com, 5d49f482552c9b6, RESELLER
sonobi.com, 83729e979b, RESELLER
33across.com, 0010b00002cGp2AAAS, RESELLER, bbea06d9c4d2853c
rhythmone.com, 3611299104, RESELLER, a670c89d4a324e47
districtm.io, 100600, RESELLER
lemmatechnologies.com, 399, RESELLER, 7829010c5bebd1fb #LEMMA
e-planning.net,ec771b05828a67fa,RESELLER,c1ba615865ed87b2
google.com, pub-9685734445476814, RESELLER, f08c47fec0942fa0
#EMX Digital
emxdgt.com, 2345, RESELLER, 1e1d41537f7cad7f
#The MediaGrid
themediagrid.com, B8ZEVT, RESELLER, 35d5010d7789b49d
themediagrid.com, 3W8S2K, RESELLER, 35d5010d7789b49d
#triplelift
triplelift.com, 12900, RESELLER, 6c33edb13117fd86
triplelift.com, 12900-EB, DIRECT, 6c33edb13117fd86
triplelift.com, 13897, DIRECT, 6c33edb13117fd86
#Sharethrough
sharethrough.com, buaxQzOE, RESELLER, d53b998a7bd4ecd2
sharethrough.com, jvyAFD6e, DIRECT, d53b998a7bd4ecd2
pubmatic.com, 156557, RESELLER, 5d62403b186f2ace
rubiconproject.com, 18694, RESELLER, 0bfd66d529a55807
openx.com, 540274407, RESELLER, 6a698e2ec38604c6
33across.com, 0013300001kQj2HAAS, RESELLER, bbea06d9c4d2853c
smaato.com, 1100047713, RESELLER, 07bcf65f187117b4
yahoo.com, 59531, RESELLER, e1a5b5b6e3255540
smartadserver.com, 4342, RESELLER
smartadserver.com, 4012, RESELLER
#V 15.01.2024 PH
#------------------------------------------------------------------------------------------------------
adagio.io, 1090, DIRECT # Adagio_0_6
rubiconproject.com, 19116, RESELLER, 0bfd66d529a55807 # Adagio_0_6
pubmatic.com, 159110, RESELLER, 5d62403b186f2ace # Adagio_0_6
improvedigital.com, 1790, RESELLER # Adagio_0_6
indexexchange.com, 194558, RESELLER # Adagio_0_6
richaudience.com, 1BTOoaD22a, DIRECT # Adagio_0_6
33across.com, 0015a00002oUk4aAAC, DIRECT, bbea06d9c4d2853c # Adagio_0_6
appnexus.com, 10239, RESELLER, f5ab79cb980f11d1 # Adagio_0_6
rubiconproject.com, 16414, RESELLER, 0bfd66d529a55807 # Adagio_0_6
lijit.com, 367236, RESELLER, fafdf38b16bf6b2b # Adagio_0_6
e-planning.net, 83c06e81531537f4, RESELLER, c1ba615865ed87b2 # Adagio_0_6
amxrtb.com, 105199358, DIRECT # AdaptMX_1_6&7
indexexchange.com, 191503, RESELLER # AdaptMX_1_6&7
appnexus.com, 11786, RESELLER # AdaptMX_1_6&7
appnexus.com, 12290, RESELLER # AdaptMX_1_6&7
pubmatic.com, 158355, RESELLER, 5d62403b186f2ace # AdaptMX_1_6&7
advertising.com, 28305, RESELLER # AdaptMX_1_6&7
rubiconproject.com, 23844, RESELLER, 0bfd66d529a55807 # AdaptMX_1_6&7
openx.com, 559680764, RESELLER, 6a698e2ec38604c6 # AdaptMX_1_6&7
adform.com, 2767, RESELLER # Adform_0_6&7
adyoulike.com, c1314a52de718f3c214c00173d2994f9, DIRECT # AdYouLike_0_6
pubmatic.com, 160925, RESELLER, 5d62403b186f2ace # AdYouLike_0_6
rubiconproject.com, 20736, RESELLER, 0bfd66d529a55807 # AdYouLike_0_6
appnexus.com, 7664, RESELLER # AdYouLike_0_6
aps.amazon.com,70247b00-ff8f-4016-b3ab-8344daf96e09,DIRECT # Amazon_3_6&7
ad-generation.jp,12474,RESELLER # Amazon_3_6&7
aniview.com, 5f2063121d82c82557194737, RESELLER, 78b21b97965ec3f8 # Aniview
aniview.com, 643f8e74688b10f72307cc24, DIRECT, 78b21b97965ec3f8 # Aniview
google.com, pub-6346866704322274, RESELLER, f08c47fec0942fa0 # Aniview
pubmatic.com, 160993, RESELLER, 5d62403b186f2ace # Aniview
rubiconproject.com, 13918, RESELLER, 0bfd66d529a55807 # Aniview
google.com, pub-5717092533913515, RESELLER, f08c47fec0942fa0 # Aniview
gannett.com, 22652678936, RESELLER # Aniview
richaudience.com, 1ru8dKmJJV, RESELLER # Aniview
appnexus.com, 12637, RESELLER, f5ab79cb980f11d1 # Aniview
google.com, pub-3565385483761681, RESELLER, f08c47fec0942fa0 # Aniview
sharethrough.com, zLsEa05k, RESELLER, d53b998a7bd4ecd2 # Aniview
aps.amazon.com, 1ad7261b-91ea-4b6f-b9e9-b83522205b75, RESELLER # Aniview
pubmatic.com, 161335, RESELLER, 5d62403b186f2ace # Aniview
google.com, pub-7734005103835923, RESELLER, f08c47fec0942fa0 # Aniview
openx.com, 559611024, RESELLER, 6a698e2ec38604c6 # Aniview
yieldlab.net, 495507, DIRECT # Aniview
blockthrough.com, 5643766199222272, DIRECT # Blockthrough
appnexus.com, 6979, RESELLER # Blockthrough
indexexchange.com, 194341, RESELLER, 50b1c356f2c5c8fc # Blockthrough
pubmatic.com, 160377, RESELLER, 5d62403b186f2ace # Blockthrough
rubiconproject.com, 23718, RESELLER, 0bfd66d529a55807 # Blockthrough
onetag.com, 75804861b76a852, DIRECT # Blockthrough
amxrtb.com, 105199664, DIRECT # Blockthrough
criteo.com, B-062405, DIRECT, 9fac4a4a87c2a44f # Criteo_0_6&7
themediagrid.com, CVQXOH, DIRECT, 35d5010d7789b49d # Criteo_0_6&7
cpmstar.com, 53615, DIRECT # CPMSTAR
rhythmone.com,1838093862,DIRECT,a670c89d4a324e47 # CPMSTAR
video.unrulymedia.com, 1838093862, DIRECT # CPMSTAR
pubmatic.com, 160251, DIRECT, 5d62403b186f2ace # CPMSTAR
pubmatic.com, 161595, DIRECT, 5d62403b186f2ace # CPMSTAR
rubiconproject.com, 23330, DIRECT, 0bfd66d529a55807 # CPMSTAR
conversantmedia.com, 41150, DIRECT, 03113cd04947736d # Epsilon
adingo.jp, 24379, DIRECT # Fluct_1_6&7
freewheel.tv, 211121, DIRECT # Freewheel_0_7
freewheel.tv, 211129, RESELLER # Freewheel_0_7
google.com, pub-5781531207509232, RESELLER, f08c47fec0942fa0 # Google_AdX_6&7
google.com, pub-2553634189837243, RESELLER, f08c47fec0942fa0 # Google_AdX_6&7
gumgum.com, 13385, RESELLER, ffdef49475d318a9 # GumGum_JP_0_9_6
gumgum.com, 14302, RESELLER, ffdef49475d318a9 # GumGum_JP_0_9_6
improvedigital.com, 1012, DIRECT # Improve_0_6&7
improvedigital.com, 1640, RESELLER # Improve_1_6
improvedigital.com, 2114, RESELLER # Improve_kids_1_6&7
indexexchange.com, 183921, DIRECT, 50b1c356f2c5c8fc # Index Exchange_0_6&7
indexexchange.com, 188416, DIRECT, 50b1c356f2c5c8fc # Index Exchange_1_6&7
indexexchange.com, 193067, DIRECT, 50b1c356f2c5c8fc # Index Exchange_2_6&7
indexexchange.com, 194127, DIRECT, 50b1c356f2c5c8fc # Index Exchange_7&4_6&7
indexexchange.com, 205972, RESELLER, 50b1c356f2c5c8fc # Index Exchange_Oz
indexexchange.com, 206870, RESELLER, 50b1c356f2c5c8fc # Index_EasyConnect
iion.io, 10133, DIRECT # iion
kargo.com, 8688, DIRECT # Kargo_0_6
rubiconproject.com, 17902, RESELLER, 0bfd66d529a55807 # Magnite_1_6&7
rubiconproject.com, 13762, RESELLER, 0bfd66d529a55807 # Magnite_0&2_6&7
telaria.com,hpwve,RESELLER,1a4e959a1b50034a # Magnite_Streaming
tremorhub.com,hpwve,RESELLER,1a4e959a1b50034a # Magnite_Streaming
media.net, 8CU8ARTF8, DIRECT # Media.net
Media.net, 8CU5786QK, DIRECT # Media.net
themediagrid.com, LTW57M, DIRECT, 35d5010d7789b49d # MediaGrid_2_6&7
minutemedia.com, 01gerz6y43ck, RESELLER # MinuteMedia_0_6
pubmatic.com, 161683, RESELLER, 5d62403b186f2ace # MinuteMedia_0_6
appnexus.com, 8381, RESELLER # MinuteMedia_0_6
triplelift.com, 6030, RESELLER, 6c33edb13117fd86 # MinuteMedia_0_6
33across.com, 0013300001jlr99AAA, RESELLER, bbea06d9c4d2853c # MinuteMedia_0_6
nobid.io, 22629800915, DIRECT # Nobid_0_6
sonobi.com, 7ad1b9f952, RESELLER, d1a215d9eb5aee9e # Nobid_0_6
xandr.com, 12701, RESELLER, f5ab79cb980f11d1 # Nobid_0_6
lijit.com, 273657, DIRECT, fafdf38b16bf6b2b # Nobid_0_6
onetag.com, 694e68b73971b58, DIRECT # Nobid_0_6
yahoo.com, 57872, RESELLER # Nobid_0_6
sharethrough.com, UvcAx8IL, DIRECT, d53b998a7bd4ecd2 # Nobid_0_6
ogury.com, 086233d2-e8a8-44fc-907b-f0752e1c85de, DIRECT # Ogury_0_6
appnexus.com, 11470, RESELLER # Ogury_0_6
openx.com, 537144009, RESELLER, 6a698e2ec38604c6 # OpenX_0_6
openx.com, 540134228, RESELLER, 6a698e2ec38604c6 # OpenX_0_7
openx.com, 540368327, RESELLER, 6a698e2ec38604c6 # OpenX_1_6&7
openx.com, 542378302, RESELLER, 6a698e2ec38604c6 # OpenX_2_6&7
the-ozone-project.com, ozoneven0005, DIRECT # Ozone_0_6
appnexus.com, 9979, RESELLER # Ozone_0_6
openx.com, 540731760, RESELLER, 6a698e2ec38604c6 # Ozone_0_6
adform.com, 2657, RESELLER, 9f5210a2f0999e32 # Ozone_0_6
pubmatic.com, 160557, RESELLER, 5d62403b186f2ace # Ozone_0_6
themediagrid.com, WF71T3, DIRECT, 35d5010d7789b49d # Ozone_0_6
pgamssp.com, 634dc90283fff00f005151f2, DIRECT # PGAM_0_7
freewheel.tv, 1489202, RESELLER # PGAM_0_7
freewheel.tv, 1488706, RESELLER # PGAM_0_7
video.unrulymedia.com, 5921144960123684292, RESELLER # PGAM_0_7
appnexus.com, 9291, RESELLER # PGAM_0_7
pubmatic.com, 162623, RESELLER, 5d62403b186f2ace # PGAM_0_7
primis.tech, 31136, DIRECT, b6b21d256ef43532 # Primis
pubmatic.com, 156595, RESELLER, 5d62403b186f2ace # Primis
google.com, pub-1320774679920841, RESELLER, f08c47fec0942fa0 # Primis
openx.com, 540258065, RESELLER, 6a698e2ec38604c6 # Primis
rubiconproject.com, 20130, RESELLER, 0bfd66d529a55807 # Primis
freewheel.tv, 19133, RESELLER, 74e8e47458f74754 # Primis
smartadserver.com, 3436, RESELLER, 060d053dcf45cbf3 # Primis
indexexchange.com, 191923, RESELLER, 50b1c356f2c5c8fc # Primis
adform.com, 2078, RESELLER # Primis
Media.net, 8CU695QH7, RESELLER # Primis
video.unrulymedia.com, 2338962694, RESELLER # Primis
sharethrough.com, flUyJowI, RESELLER, d53b998a7bd4ecd2 # Primis
triplelift.com, 8210, RESELLER, 6c33edb13117fd86 # Primis
yahoo.com, 59260, RESELLER # Primis
pubmatic.com, 159234, RESELLER, 5d62403b186f2ace # PubMatic_0_6&7
pubmatic.com, 158940, RESELLER, 5d62403b186f2ace # PubMatic_1_6&7
pubmatic.com, 160552, RESELLER, 5d62403b186f2ace # PubMatic_4_7
pubmatic.com, 159401, RESELLER, 5d62403b186f2ace # PubMatic_2_6&7
pubmatic.com, 163598, RESELLER, 5d62403b186f2ace # Pubmatic_OW
richaudience.com, 1XvIoD5o0S, DIRECT # Rich Audience_0_6&7
risecodes.com, 5fa94677b2db6a00015b22a9, DIRECT # Rise
pubmatic.com, 160295, RESELLER, 5d62403b186f2ace # Rise
xandr.com, 14082, RESELLER # Rise
rubiconproject.com, 23876, RESELLER, 0bfd66d529a55807 # Rise
media.net, 8CUQ6928Q, RESELLER # Rise_Temp
sharethrough.com, 5926d422, RESELLER, d53b998a7bd4ecd2 # Rise_Temp
sharethrough.com, 31c129df, DIRECT, d53b998a7bd4ecd2 # Sharethrough_0_6&7
sharethrough.com, Ip2TfKpa, DIRECT, d53b998a7bd4ecd2 # Sharethrough_1_6&7
smartadserver.com, 2161, RESELLER # Showheroes_7_8
appnexus.com, 8833, RESELLER, f5ab79cb980f11d1 # Showheroes_7_8
smartadserver.com, 3668, RESELLER # Showheroes_7_8
freewheel.tv, 1003361, DIRECT # Showheroes_7_8
pubmatic.com, 156695, DIRECT, 5d62403b186f2ace # Showheroes_7_8
showheroes.com, 6829, RESELLER # Showheroes_7_8
smartadserver.com, 3490, DIRECT # Smart AdServer_0&1&2_6&7
smartadserver.com, 3490-OB, DIRECT, 060d053dcf45cbf3 # Smart AdServer_0&1&2_6&7
smartadserver.com, 4016, DIRECT # Smart AdServer_0&1&2_6&7
smartadserver.com, 4074, DIRECT # Smart AdServer_0&1&2_6&7
smaato.com, 1100055690, DIRECT, 07bcf65f187117b4 # Smaato
smaato.com, 1100004890, DIRECT, 07bcf65f187117b4 # Smaato
sonobi.com, 116da9d98c, DIRECT, d1a215d9eb5aee9e # Sonobi_0_6&7
sonobi.com, e017850301, DIRECT, d1a215d9eb5aee9e # Sonobi_4_7
sovrn.com, 237754, DIRECT, fafdf38b16bf6b2b # Sovrn_0&1&2_6&7
lijit.com, 237754, DIRECT, fafdf38b16bf6b2b # Sovrn_0&1&2_6&7
lijit.com, 237754-eb, DIRECT, fafdf38b16bf6b2b # Sovrn_1_6&7
taboola.com,1422403,DIRECT,c228e6794e811952 # Taboola_6_8
triplelift.com, 6059, DIRECT, 6c33edb13117fd86 # Triplelift_0&2_6&7
triplelift.com, 6059-EB, DIRECT, 6c33edb13117fd86 # Triplelift_0&2_6&7
video.unrulymedia.com, 985572675, DIRECT # Unruly_0&2_7
rhythmone.com, 2864567592, DIRECT, a670c89d4a324e47 # Unruly_0&2_7
xandr.com, 13799, RESELLER # Unruly
sharethrough.com, 6qlnf8SY, RESELLER, d53b998a7bd4ecd2 # Unruly
vidazoo.com, 655c85dc63ceeb606a0f365f, DIRECT, b6ada874b4d7d0b2 # Vidazoo
pubmatic.com, 159988, RESELLER, 5d62403b186f2ace # Vidazoo
rubiconproject.com, 17130, RESELLER, 0bfd66d529a55807 # Vidazoo
pubmatic.com, 156512, DIRECT # Wunderkind
indexexchange.com, 183753, DIRECT # Wunderkind
wunderkind.co, 6438, DIRECT # Wunderkind
wunderkind.co, 6449, DIRECT # Wunderkind
criteo.com, B-068503, DIRECT # Wunderkind
appnexus.com, 806, DIRECT, f5ab79cb980f11d1 # Xandr_0&2_6&7
appnexus.com,1908,RESELLER,f5ab79cb980f11d1 # Xandr_0&2_6&7
#Equativ
smartadserver.com, 4571, RESELLER, 060d053dcf45cbf3
smartadserver.com, 4571-OB, RESELLER, 060d053dcf45cbf3
smartadserver.com, 4016, RESELLER, 060d053dcf45cbf3 #Global
smartadserver.com, 4012, RESELLER, 060d053dcf45cbf3 #EUR
smartadserver.com, 4071, RESELLER, 060d053dcf45cbf3 #USD
smartadserver.com, 4073, RESELLER, 060d053dcf45cbf3 #BRL
smartadserver.com, 4074, RESELLER, 060d053dcf45cbf3 #MXN
smartadserver.com, 4247, RESELLER, 060d053dcf45cbf3 #CAD
smartadserver.com, 4228, RESELLER, 060d053dcf45cbf3 #USD_CTV
pubmatic.com, 156439, RESELLER, 5d62403b186f2ace
pubmatic.com, 154037, RESELLER, 5d62403b186f2ace
rubiconproject.com, 16114, RESELLER, 0bfd66d529a55807
openx.com, 537149888, RESELLER, 6a698e2ec38604c6
appnexus.com, 3703, RESELLER, f5ab79cb980f11d1
loopme.com, 5679, RESELLER, 6c8d5f95897a5a3b
xad.com, 958, RESELLER, 81cbf0a75a5e0e9a
video.unrulymedia.com, 2564526802, RESELLER
smaato.com, 1100044045, RESELLER, 07bcf65f187117b4
pubnative.net, 1006576, RESELLER, d641df8625486a7b
verve.com, 15503, RESELLER, 0c8f5958fc2d6270
adyoulike.com, b4bf4fdd9b0b915f746f6747ff432bde, RESELLER, 4ad745ead2958bf7
axonix.com, 57264, RESELLER
admanmedia.com, 43, RESELLER
sharethrough.com, OAW69Fon, RESELLER, d53b998a7bd4ecd2
contextweb.com, 560288, RESELLER, 89ff185a4c4e857c
#nobid
nobid.io, 22931676975, DIRECT
xandr.com, 11429, RESELLER, f5ab79cb980f11d1
sharethrough.com, aRE1degH, RESELLER, d53b998a7bd4ecd2
sonobi.com, 7ad1b9f952, RESELLER, d1a215d9eb5aee9e
sharethrough.com, UvcAx8IL, RESELLER, d53b998a7bd4ecd2
amxrtb.com, 105199579, RESELLER
yahoo.com,49648,RESELLER
rubiconproject.com, 24434, RESELLER, 0bfd66d529a55807
minutemedia.com, 01gerz67grgj, RESELLER
pubmatic.com, 161683, RESELLER, 5d62403b186f2ace
appnexus.com, 8381, RESELLER, f5ab79cb980f11d1
triplelift.com, 6030, RESELLER, 6c33edb13117fd86
sonobi.com, 37fbaf262c, RESELLER, d1a215d9eb5aee9e
openx.com, 540780517, RESELLER, 6a698e2ec38604c6
rubiconproject.com, 17598, RESELLER, 0bfd66d529a55807
indexexchange.com, 196326, RESELLER, 50b1c356f2c5c8fc
yahoo.com, 59407, RESELLER, e1a5b5b6e3255540
sharethrough.com, xz7QjFBY, RESELLER, d53b998a7bd4ecd2
inmobi.com,8f261ace12c3486ba2e0d2011cd97976,RESELLER,83e75a7ae333ca9d
risecodes.com, 63ea59eef828de0001cf1773, RESELLER
inmobi.com, 9e311c7a68e94888aac7fbb4272381e2, RESELLER, 83e75a7ae333ca9d
video.unrulymedia.com, 1352466146, RESELLER
yahoo.com, 59261, RESELLER, e1a5b5b6e3255540
gumgum.com, 13926, RESELLER, ffdef49475d318a9
onetag.com, 694e68b73971b58, RESELLER
lijit.com, 273657, RESELLER, fafdf38b16bf6b2b
sovrn.com, 273657, RESELLER, fafdf38b16bf6b2b
mediafuse.com, 389, RESELLER
appnexus.com, 9538, RESELLER, f5ab79cb980f11d1
yahoo.com, 57872, RESELLER
video.unrulymedia.com, 2997140015, RESELLER
indexexchange.com, 182257, RESELLER, 50b1c356f2c5c8fc
152media.info,152M374,RESELLER
appnexus.com, 3153, RESELLER, f5ab79cb980f11d1
#media.net_serverside_displayvideo
media.net, 8CUV34PJ4, DIRECT
sharethrough.com, koRtppYA, RESELLER, d53b998a7bd4ecd2
video.unrulymedia.com, 699546687, RESELLER
lijit.com, 264726, RESELLER, fafdf38b16bf6b2b
onetag.com, 765b4e6bb9c8438, RESELLER
amxrtb.com, 105199663, RESELLER
yieldmo.com, 2954622693783052507, RESELLER
loopme.com, 11556, RESELLER, 6c8d5f95897a5a3b
Contextweb.com, 562963, RESELLER, 89ff185a4c4e857c
zeta.com, 591, RESELLER
disqus.com, 591, RESELLER
admanmedia.com, 953, RESELLER
smartadserver.com, 4106, RESELLER, 060d053dcf45cbf3
imds.tv, 82302, RESELLER, ae6c32151e71f19d
improvedigital.com, 2073, RESELLER
betweendigital.com, 44808, RESELLER
adyoulike.com, 53264963677efeda057eef7db2cb305f, RESELLER
freewheel.tv,1577878,RESELLER
freewheel.tv,1577888,RESELLER
dxkulture.com, 9533, DIRECT, 259726033fc4df0c
dxkulture.com, 0098, DIRECT, 259726033fc4df0c
adswizz.com,dxkulture,DIRECT
adswizz.com,651,DIRECT
pubmatic.com,164751,RESELLER,5d62403b186f2ace
rubiconproject.com,26094,DIRECT,0bfd66d529a55807
zetaglobal.net,790,DIRECT
ssp.disqus.com,790,DIRECT
video.unrulymedia.com,946176315,RESELLER
video.unrulymedia.com, 347774562, RESELLER
rubiconproject.com, 15268, RESELLER, 0bfd66d529a55807
pubmatic.com, 159277, RESELLER
#AdaptMX
amxrtb.com, 105199723, DIRECT
appnexus.com, 12290, RESELLER
pubmatic.com, 161527, RESELLER
rubiconproject.com, 23844, RESELLER
# Adagio
adagio.io, 1361, RESELLER
# Adagio - Magnite
rubiconproject.com, 19116, RESELLER, 0bfd66d529a55807
# Adagio - Pubmatic
pubmatic.com, 159110, RESELLER, 5d62403b186f2ace
# Adagio - Improve Digital
improvedigital.com, 1790, RESELLER
# Adagio - Onetag
onetag.com, 6b859b96c564fbe, RESELLER
appnexus.com, 13099, RESELLER
pubmatic.com, 161593, RESELLER, 5d62403b186f2ace
# Adagio - Index Exchange
indexexchange.com, 194558, RESELLER
# Adagio - 33Across
33across.com, 0015a00002oUk4aAAC, RESELLER, bbea06d9c4d2853c
yahoo.com, 57289, RESELLER, e1a5b5b6e3255540
appnexus.com, 10239, RESELLER, f5ab79cb980f11d1
rubiconproject.com, 16414, RESELLER, 0bfd66d529a55807
pubmatic.com, 156423, RESELLER, 5d62403b186f2ace
rubiconproject.com, 21642, RESELLER, 0bfd66d529a55807
conversantmedia.com, 100141, RESELLER
indexexchange.com, 191973, RESELLER, 50b1c356f2c5c8fc
triplelift.com, 12503, RESELLER, 6c33edb13117fd86
insticator.com, 4ec3ed85-2830-4174-9f7f-f545620598b9, RESELLER
sharethrough.com, Q9IzHdvp, RESELLER, d53b998a7bd4ecd2
admanmedia.com, 2216, RESELLER
connectad.io, 456, RESELLER, 85ac85a30c93b3e5
# Adagio - Equativ
smartadserver.com, 3554, RESELLER
# Adagio - Sovrn
lijit.com, 367236, RESELLER, fafdf38b16bf6b2b
# Adagio - Freewheel
freewheel.tv, 1568036, RESELLER
freewheel.tv, 1568041, RESELLER
# Adagio - OpenX
openx.com, 558899373, RESELLER, 6a698e2ec38604c6
# Adagio - Triplelift
triplelift.com, 13482, RESELLER, 6c33edb13117fd86
# Adagio - E-Planning
e-planning.net, 83c06e81531537f4, RESELLER, c1ba615865ed87b2
pubmatic.com, 156631, RESELLER, 5d62403b186f2ace
openx.com, 541031350, RESELLER, 6a698e2ec38604c6
rubiconproject.com, 12186, RESELLER, 0bfd66d529a55807
# Adagio - Nexxen
video.unrulymedia.com, 5672421953199218469, RESELLER
#Freewheel
freewheel.tv, 1598995, RESELLER
freewheel.tv, 1599004, RESELLER
#Pgam
pgamssp.com, 64661fa49d522e327b0a8b84, DIRECT
freewheel.tv, 1489202, RESELLER
freewheel.tv, 1488706, RESELLER
rubiconproject.com, 24852, RESELLER, 0bfd66d529a55807
pubmatic.com, 162623, RESELLER, 5d62403b186f2ace
video.unrulymedia.com, 5921144960123684292, RESELLER
appnexus.com, 9291, RESELLER, f5ab79cb980f11d1
#Sonobi
sonobi.com, 3ee2ca3952, RESELLER, d1a215d9eb5aee9e
#Rich Audience
richaudience.com, kWVs0vbyki, RESELLER
appnexus.com, 2928, DIRECT, f5ab79cb980f11d1
smartadserver.com, 1999, RESELLER, 060d053dcf45cbf3
#Ozone
the-ozone-project.com, OZONEAIP0001, DIRECT
appnexus.com, 9979, RESELLER, f5ab79cb980f11d1
openx.com, 540731760, RESELLER, 6a698e2ec38604c6
adform.com, 2657, RESELLER, 9f5210a2f0999e32
pubmatic.com, 160557, RESELLER, 5d62403b186f2ace
indexexchange.com, 206233, RESELLER, 50b1c356f2c5c8fc
themediagrid.com, 1J3ZI6, DIRECT, 35d5010d7789b49d
themediagrid.com, WF71T3, DIRECT, 35d5010d7789b49d
# OptiDigital
optidigital.com,p345,RESELLER
pubmatic.com,158939,RESELLER,5d62403b186f2ace
rubiconproject.com,20336,RESELLER,0bfd66d529a55807
smartadserver.com,3379,RESELLER,060d053dcf45cbf3
criteo.com,B-060926,RESELLER,9fac4a4a87c2a44f
themediagrid.com,3ETIX5,RESELLER,35d5010d7789b49d
triplelift.com,8183,RESELLER,6c33edb13117fd86
appnexus.com,12190,RESELLER,f5ab79cb980f11d1
onetag.com,806eabb849d0326,RESELLER
rtbhouse.com,mSu1piUSmB9TF4AQDGk4,RESELLER
33across.com,001Pg00000HMy0YIAT,RESELLER,bbea06d9c4d2853c
e-planning.net,a76893b96338e7e9,RESELLER,c1ba615865ed87b2
appnexus.com,15941,RESELLER,f5ab79cb980f11d1
video.unrulymedia.com,731539260,RESELLER
#Rise
risecodes.com, 643813aab7212c00011c3f28, DIRECT
pubmatic.com, 160295, RESELLER, 5d62403b186f2ace
xandr.com, 14082, RESELLER
rubiconproject.com, 23876, RESELLER, 0bfd66d529a55807
sharethrough.com, 5926d422, RESELLER, d53b998a7bd4ecd2
media.net, 8CUQ6928Q, RESELLER
sonobi.com, 4a289cdd79, RESELLER, d1a215d9eb5aee9e
video.unrulymedia.com, 335119963, RESELLER
contextweb.com,562615,RESELLER,89ff185a4c4e857c
onetag.com, 69f48c2160c8113, RESELLER
33across.com, 0010b00002Xbn7QAAR, RESELLER, bbea06d9c4d2853c
yieldmo.com, 2754490424016969782, RESELLER
openx.com, 537140488, RESELLER, 6a698e2ec38604c6
lijit.com, 405318, RESELLER, fafdf38b16bf6b2b
themediagrid.com, 4DQHAP, RESELLER, 35d5010d7789b49d
loopme.com, 11362, RESELLER, 6c8d5f95897a5a3b
amxrtb.com, 105199691, RESELLER
smartadserver.com, 4284, RESELLER
adform.com, 3119, RESELLER, 9f5210a2f0999e32
smaato.com, 1100057444, RESELLER, 07bcf65f187117b4
adyoulike.com, 78afbc34fac571736717317117dfa247, RESELLER
#Block
blockthrough.com, 5130683165442048, DIRECT
pubmatic.com, 160377, RESELLER, 5d62403b186f2ace
rubiconproject.com, 23718, RESELLER, 0bfd66d529a55807
appnexus.com, 6979, RESELLER
lijit.com, 251666, RESELLER, fafdf38b16bf6b2b
lijit.com, 251666-eb, RESELLER, fafdf38b16bf6b2b
video.unrulymedia.com, 2444764291, RESELLER
contextweb.com, 558511, RESELLER
krushmedia.com, AJxF6R572a9M6CaTvK, RESELLER
criteo.com, 8990, RESELLER
smartadserver.com, 4485, RESELLER, 060d053dcf45cbf3
smartadserver.com, 4485-OB, RESELLER, 060d053dcf45cbf3
Contextweb.com, 562926, RESELLER, 89ff185a4c4e857c
# VT Amazon TAM
aps.amazon.com,70247b00-ff8f-4016-b3ab-8344daf96e09,DIRECT
indexexchange.com, 193067, DIRECT, 50b1c356f2c5c8fc
triplelift.com, 6059, DIRECT, 6c33edb13117fd86
sharethrough.com, 31c129df, DIRECT, d53b998a7bd4ecd2
appnexus.com, 806, DIRECT, f5ab79cb980f11d1
risecodes.com, 5fa94677b2db6a00015b22a9, DIRECT
minutemedia.com, 01gerz6y43ck, RESELLER
themediagrid.com, LTW57M, DIRECT, 35d5010d7789b49d
vidazoo.com, 655c85dc63ceeb606a0f365f, DIRECT, b6ada874b4d7d0b2
smartadserver.com, 3490, DIRECT
##################################
# AdinPlay.com ads.txt - 2025-04-16
##################################
venatus.com, OFI, DIRECT
Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

+88 -4
View File
@@ -1,5 +1,89 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg xmlns="http://www.w3.org/2000/svg" version="1.1" width="512px" height="512px" style="shape-rendering:geometricPrecision; text-rendering:geometricPrecision; image-rendering:optimizeQuality; fill-rule:evenodd; clip-rule:evenodd" xmlns:xlink="http://www.w3.org/1999/xlink">
<g><path fill="#9e0000" d="M 57.5,16.5 C 79.6232,16.1252 88.4565,26.7919 84,48.5C 83.2196,51.5117 82.0529,54.3451 80.5,57C 111.436,74.9338 136.936,98.7672 157,128.5C 182.331,109.668 210.831,100.334 242.5,100.5C 242.5,106.833 242.5,113.167 242.5,119.5C 215.494,119.225 191.16,127.058 169.5,143C 181.768,144.418 194.101,145.418 206.5,146C 223.046,163.709 239.379,181.543 255.5,199.5C 271.621,181.543 287.954,163.709 304.5,146C 316.899,145.418 329.232,144.418 341.5,143C 319.84,127.058 295.506,119.225 268.5,119.5C 268.5,113.167 268.5,106.833 268.5,100.5C 300.169,100.334 328.669,109.668 354,128.5C 374.064,98.7672 399.564,74.9338 430.5,57C 422.929,44.1613 423.929,32.1613 433.5,21C 441.053,16.4447 449.053,15.4447 457.5,18C 478.079,25.4111 490.079,39.9111 493.5,61.5C 490.867,80.2657 480.201,87.4324 461.5,83C 458.488,82.2196 455.655,81.0529 453,79.5C 435.066,110.436 411.233,135.936 381.5,156C 400.886,181.491 410.72,210.324 411,242.5C 404.924,243.476 398.758,243.81 392.5,243.5C 391.875,215.457 383.375,190.124 367,167.5C 365.477,180.07 364.477,192.737 364,205.5C 344.658,222.677 325.492,240.01 306.5,257.5C 367.877,327.636 426.377,399.969 482,474.5C 486.572,480.683 490.572,487.016 494,493.5C 465.55,472.718 437.384,451.551 409.5,430C 357.697,387.864 306.197,345.364 255,302.5C 178.278,369.57 98.9443,433.237 17,493.5C 20.4282,487.016 24.4282,480.683 29,474.5C 84.5044,400.043 143.004,327.876 204.5,258C 185.762,240.273 166.596,222.773 147,205.5C 146.523,192.737 145.523,180.07 144,167.5C 127.625,190.124 119.125,215.457 118.5,243.5C 112.242,243.81 106.076,243.476 100,242.5C 100.28,210.324 110.114,181.491 129.5,156C 99.7672,135.936 75.9338,110.436 58,79.5C 46.6724,86.2353 35.5058,86.0686 24.5,79C 18.004,71.1766 16.1706,62.3433 19,52.5C 25.9173,34.08 38.7506,22.08 57.5,16.5 Z"/></g>
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
version="1.1"
x="0px"
y="0px"
viewBox="0 0 100 100"
id="svg10"
sodipodi:docname="TraitorIcon.svg"
width="100"
height="100"
inkscape:version="1.1.2 (0a00cf5339, 2022-02-04)"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<defs
id="defs14" />
<sodipodi:namedview
id="namedview12"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageshadow="2"
inkscape:pageopacity="0.0"
inkscape:pagecheckerboard="0"
showgrid="false"
height="100px"
inkscape:zoom="6.984"
inkscape:cx="49.971363"
inkscape:cy="62.142039"
inkscape:window-width="3072"
inkscape:window-height="1653"
inkscape:window-x="0"
inkscape:window-y="38"
inkscape:window-maximized="1"
inkscape:current-layer="svg10" />
<g
transform="translate(0,-952.36218)"
id="g4">
<path
style="color:#000000;text-indent:0;text-transform:none;direction:ltr;baseline-shift:baseline;enable-background:accumulate"
d="m 49.682164,957.35229 c -0.08371,0.007 -0.167129,0.0173 -0.249965,0.0312 l -35.995035,7.00077 C 12.072501,964.6449 10.989932,965.96376 11,967.35339 v 32.00351 c 0.0012,0.13594 0.01165,0.27179 0.03125,0.4063 1.29114,9.0104 3.853487,15.5969 8.498827,22.2837 0.990121,1.4292 3.318267,1.6525 4.561871,0.4375 4.250005,-4.1812 7.885056,-7.78 11.967099,-11.9697 0.44046,-0.4164 0.750052,-0.9694 0.87488,-1.5626 0.628358,-3.8527 1.729676,-9.31167 2.530901,-13.59583 h 15.529108 c 1.243283,0.002 2.441795,-0.85453 2.843357,-2.03147 l 9.998621,-29.00319 c 0.567687,-1.6172 -0.568544,-3.6061 -2.249689,-3.93794 l -14.997932,-3.00033 c -0.298144,-0.0562 -0.604826,-0.0667 -0.906125,-0.0312 z m 23.902953,5.00055 c -1.078435,0.15321 -2.042235,0.94196 -2.405918,1.96897 L 58.899643,998.35679 H 44.995311 c -1.406702,-0.005 -2.733797,1.11295 -2.968341,2.50031 l -1.81225,10.9699 -14.27928,13.3139 c -1.128859,1.0576 -1.217322,3.0338 -0.187474,4.188 6.269465,7.139 13.562725,12.4576 22.778108,17.627 0.892103,0.5009 2.044992,0.5009 2.937095,0 19.535202,-10.9581 34.280717,-24.7622 37.494828,-47.1927 0.06997,-10.76177 0.03124,-21.61653 0.03124,-32.40981 0.0049,-1.40704 -1.112648,-2.73446 -2.499655,-2.96908 l -11.99834,-2.00021 c -0.298144,-0.0562 -0.604826,-0.0667 -0.906125,-0.0312 z m -23.621742,1.06261 11.060975,2.18774 -8.186371,23.75262 H 36.996414 c -1.378861,0.004 -2.67845,1.08304 -2.937095,2.43777 l -2.812112,15.06422 c -2.889686,2.9098 -5.919847,5.9695 -8.686302,8.7197 -2.891489,-4.8594 -4.568269,-9.6725 -5.561733,-16.50187 v -29.25322 z m 25.996415,5.28184 7.03028,1.18763 v 29.19071 c -2.868962,19.61817 -15.058331,31.46397 -32.995449,41.78587 -7.128175,-4.1138 -12.790202,-8.2954 -17.716306,-13.4077 l 12.779487,-11.9076 c 0.479911,-0.4525 0.804009,-1.0673 0.906125,-1.719 l 1.59353,-9.4698 h 13.435647 c 1.225447,-0.01 2.403546,-0.8446 2.812112,-2.0002 z"
fill="#000000"
fill-opacity="1"
fill-rule="nonzero"
stroke="none"
marker="none"
visibility="visible"
display="inline"
overflow="visible"
id="path2" />
</g>
<path
style="fill:#d40000;fill-opacity:0;stroke-width:0.143184"
d="M 21.229102,70.741061 C 20.081432,70.373981 19.32618,69.461642 17.270525,65.959129 14.47136,61.189795 12.54351,55.744636 11.459279,49.545428 L 11.096793,47.472871 V 30.96265 c 0,-13.527886 0.03488,-16.593449 0.193078,-16.970972 0.258937,-0.617909 0.895032,-1.354431 1.391463,-1.611145 0.222726,-0.115176 8.704097,-1.825312 18.847492,-3.8003039 l 18.442537,-3.5908935 7.910402,1.5791153 c 4.350721,0.8685133 8.143647,1.6897975 8.428725,1.8250757 0.961973,0.4564861 1.758687,1.9751714 1.568376,2.9896164 -0.047,0.250541 -2.368442,7.075757 -5.158757,15.167146 -3.44537,9.990924 -5.193245,14.868872 -5.447096,15.201688 -0.205587,0.269539 -0.643902,0.634451 -0.974034,0.810914 -0.596667,0.318933 -0.648799,0.32127 -8.759053,0.392435 l -8.158815,0.07159 -0.470445,2.434135 c -0.258746,1.338774 -0.824052,4.367125 -1.256239,6.729668 -0.432185,2.362543 -0.88633,4.5664 -1.009211,4.897461 -0.177968,0.479472 -1.505767,1.882854 -6.526761,6.898286 -3.466837,3.462998 -6.517904,6.398177 -6.78015,6.52262 -0.601484,0.285423 -1.599068,0.395138 -2.109203,0.231972 z m 5.734389,-11.793889 4.338592,-4.340341 1.438261,-7.722946 c 1.617094,-8.683222 1.602011,-8.634211 2.883301,-9.369051 l 0.745198,-0.427381 8.253581,-0.0013 c 6.783328,-0.0011 8.266754,-0.03567 8.327529,-0.194046 0.04067,-0.105988 1.891336,-5.461638 4.11259,-11.901446 2.549671,-7.391954 3.982955,-11.742017 3.8876,-11.798993 -0.08307,-0.04964 -2.590856,-0.568451 -5.572852,-1.15292 l -5.42181,-1.062727 -16.494063,3.206249 -16.494064,3.206248 v 14.900444 c 0,14.430688 0.0093,14.952195 0.295638,16.541989 0.677724,3.763097 1.813851,7.543992 3.102693,10.32539 0.832524,1.796635 2.062365,4.131162 2.176318,4.131162 0.04559,0 2.035263,-1.953154 4.421488,-4.340342 z"
id="path944" />
<path
style="fill:#d40000;fill-opacity:0;stroke-width:0.143184"
d="M 21.229102,70.741061 C 20.081432,70.373981 19.32618,69.461642 17.270525,65.959129 14.47136,61.189795 12.54351,55.744636 11.459279,49.545428 L 11.096793,47.472871 V 30.96265 c 0,-13.527886 0.03488,-16.593449 0.193078,-16.970972 0.258937,-0.617909 0.895032,-1.354431 1.391463,-1.611145 0.222726,-0.115176 8.704097,-1.825312 18.847492,-3.8003039 l 18.442537,-3.5908935 7.910402,1.5791153 c 4.350721,0.8685133 8.143647,1.6897975 8.428725,1.8250757 0.961973,0.4564861 1.758687,1.9751714 1.568376,2.9896164 -0.047,0.250541 -2.368442,7.075757 -5.158757,15.167146 -3.44537,9.990924 -5.193245,14.868872 -5.447096,15.201688 -0.205587,0.269539 -0.643902,0.634451 -0.974034,0.810914 -0.596667,0.318933 -0.648799,0.32127 -8.759053,0.392435 l -8.158815,0.07159 -0.470445,2.434135 c -0.258746,1.338774 -0.824052,4.367125 -1.256239,6.729668 -0.432185,2.362543 -0.88633,4.5664 -1.009211,4.897461 -0.177968,0.479472 -1.505767,1.882854 -6.526761,6.898286 -3.466837,3.462998 -6.517904,6.398177 -6.78015,6.52262 -0.601484,0.285423 -1.599068,0.395138 -2.109203,0.231972 z m 5.734389,-11.793889 4.338592,-4.340341 1.438261,-7.722946 c 1.617094,-8.683222 1.602011,-8.634211 2.883301,-9.369051 l 0.745198,-0.427381 8.253581,-0.0013 c 6.783328,-0.0011 8.266754,-0.03567 8.327529,-0.194046 0.04067,-0.105988 1.891336,-5.461638 4.11259,-11.901446 2.549671,-7.391954 3.982955,-11.742017 3.8876,-11.798993 -0.08307,-0.04964 -2.590856,-0.568451 -5.572852,-1.15292 l -5.42181,-1.062727 -16.494063,3.206249 -16.494064,3.206248 v 14.900444 c 0,14.430688 0.0093,14.952195 0.295638,16.541989 0.677724,3.763097 1.813851,7.543992 3.102693,10.32539 0.832524,1.796635 2.062365,4.131162 2.176318,4.131162 0.04559,0 2.035263,-1.953154 4.421488,-4.340342 z"
id="path983" />
<path
style="fill:#d40000;fill-opacity:0;stroke-width:0.143184"
d="M 21.229102,70.741061 C 20.081432,70.373981 19.32618,69.461642 17.270525,65.959129 14.47136,61.189795 12.54351,55.744636 11.459279,49.545428 L 11.096793,47.472871 V 30.96265 c 0,-13.527886 0.03488,-16.593449 0.193078,-16.970972 0.258937,-0.617909 0.895032,-1.354431 1.391463,-1.611145 0.222726,-0.115176 8.704097,-1.825312 18.847492,-3.8003039 l 18.442537,-3.5908935 7.910402,1.5791153 c 4.350721,0.8685133 8.143647,1.6897975 8.428725,1.8250757 0.961973,0.4564861 1.758687,1.9751714 1.568376,2.9896164 -0.047,0.250541 -2.368442,7.075757 -5.158757,15.167146 -3.44537,9.990924 -5.193245,14.868872 -5.447096,15.201688 -0.205587,0.269539 -0.643902,0.634451 -0.974034,0.810914 -0.596667,0.318933 -0.648799,0.32127 -8.759053,0.392435 l -8.158815,0.07159 -0.470445,2.434135 c -0.258746,1.338774 -0.824052,4.367125 -1.256239,6.729668 -0.432185,2.362543 -0.88633,4.5664 -1.009211,4.897461 -0.177968,0.479472 -1.505767,1.882854 -6.526761,6.898286 -3.466837,3.462998 -6.517904,6.398177 -6.78015,6.52262 -0.601484,0.285423 -1.599068,0.395138 -2.109203,0.231972 z m 5.734389,-11.793889 4.338592,-4.340341 1.438261,-7.722946 c 1.617094,-8.683222 1.602011,-8.634211 2.883301,-9.369051 l 0.745198,-0.427381 8.253581,-0.0013 c 6.783328,-0.0011 8.266754,-0.03567 8.327529,-0.194046 0.04067,-0.105988 1.891336,-5.461638 4.11259,-11.901446 2.549671,-7.391954 3.982955,-11.742017 3.8876,-11.798993 -0.08307,-0.04964 -2.590856,-0.568451 -5.572852,-1.15292 l -5.42181,-1.062727 -16.494063,3.206249 -16.494064,3.206248 v 14.900444 c 0,14.430688 0.0093,14.952195 0.295638,16.541989 0.677724,3.763097 1.813851,7.543992 3.102693,10.32539 0.832524,1.796635 2.062365,4.131162 2.176318,4.131162 0.04559,0 2.035263,-1.953154 4.421488,-4.340342 z"
id="path1022" />
<path
style="fill:#d40000;fill-opacity:0;stroke-width:0.143184"
d="M 21.229102,70.741061 C 20.081432,70.373981 19.32618,69.461642 17.270525,65.959129 14.47136,61.189795 12.54351,55.744636 11.459279,49.545428 L 11.096793,47.472871 V 30.96265 c 0,-13.527886 0.03488,-16.593449 0.193078,-16.970972 0.258937,-0.617909 0.895032,-1.354431 1.391463,-1.611145 0.222726,-0.115176 8.704097,-1.825312 18.847492,-3.8003039 l 18.442537,-3.5908935 7.910402,1.5791153 c 4.350721,0.8685133 8.143647,1.6897975 8.428725,1.8250757 0.961973,0.4564861 1.758687,1.9751714 1.568376,2.9896164 -0.047,0.250541 -2.368442,7.075757 -5.158757,15.167146 -3.44537,9.990924 -5.193245,14.868872 -5.447096,15.201688 -0.205587,0.269539 -0.643902,0.634451 -0.974034,0.810914 -0.596667,0.318933 -0.648799,0.32127 -8.759053,0.392435 l -8.158815,0.07159 -0.470445,2.434135 c -0.258746,1.338774 -0.824052,4.367125 -1.256239,6.729668 -0.432185,2.362543 -0.88633,4.5664 -1.009211,4.897461 -0.177968,0.479472 -1.505767,1.882854 -6.526761,6.898286 -3.466837,3.462998 -6.517904,6.398177 -6.78015,6.52262 -0.601484,0.285423 -1.599068,0.395138 -2.109203,0.231972 z m 5.734389,-11.793889 4.338592,-4.340341 1.438261,-7.722946 c 1.617094,-8.683222 1.602011,-8.634211 2.883301,-9.369051 l 0.745198,-0.427381 8.253581,-0.0013 c 6.783328,-0.0011 8.266754,-0.03567 8.327529,-0.194046 0.04067,-0.105988 1.891336,-5.461638 4.11259,-11.901446 2.549671,-7.391954 3.982955,-11.742017 3.8876,-11.798993 -0.08307,-0.04964 -2.590856,-0.568451 -5.572852,-1.15292 l -5.42181,-1.062727 -16.494063,3.206249 -16.494064,3.206248 v 14.900444 c 0,14.430688 0.0093,14.952195 0.295638,16.541989 0.677724,3.763097 1.813851,7.543992 3.102693,10.32539 0.832524,1.796635 2.062365,4.131162 2.176318,4.131162 0.04559,0 2.035263,-1.953154 4.421488,-4.340342 z"
id="path1061" />
<path
style="fill:#d40000;fill-opacity:0;stroke-width:0.143184"
d="M 21.229102,70.741061 C 20.081432,70.373981 19.32618,69.461642 17.270525,65.959129 14.47136,61.189795 12.54351,55.744636 11.459279,49.545428 L 11.096793,47.472871 V 30.96265 c 0,-13.527886 0.03488,-16.593449 0.193078,-16.970972 0.258937,-0.617909 0.895032,-1.354431 1.391463,-1.611145 0.222726,-0.115176 8.704097,-1.825312 18.847492,-3.8003039 l 18.442537,-3.5908935 7.910402,1.5791153 c 4.350721,0.8685133 8.143647,1.6897975 8.428725,1.8250757 0.961973,0.4564861 1.758687,1.9751714 1.568376,2.9896164 -0.047,0.250541 -2.368442,7.075757 -5.158757,15.167146 -3.44537,9.990924 -5.193245,14.868872 -5.447096,15.201688 -0.205587,0.269539 -0.643902,0.634451 -0.974034,0.810914 -0.596667,0.318933 -0.648799,0.32127 -8.759053,0.392435 l -8.158815,0.07159 -0.470445,2.434135 c -0.258746,1.338774 -0.824052,4.367125 -1.256239,6.729668 -0.432185,2.362543 -0.88633,4.5664 -1.009211,4.897461 -0.177968,0.479472 -1.505767,1.882854 -6.526761,6.898286 -3.466837,3.462998 -6.517904,6.398177 -6.78015,6.52262 -0.601484,0.285423 -1.599068,0.395138 -2.109203,0.231972 z m 5.734389,-11.793889 4.338592,-4.340341 1.438261,-7.722946 c 1.617094,-8.683222 1.602011,-8.634211 2.883301,-9.369051 l 0.745198,-0.427381 8.253581,-0.0013 c 6.783328,-0.0011 8.266754,-0.03567 8.327529,-0.194046 0.04067,-0.105988 1.891336,-5.461638 4.11259,-11.901446 2.549671,-7.391954 3.982955,-11.742017 3.8876,-11.798993 -0.08307,-0.04964 -2.590856,-0.568451 -5.572852,-1.15292 l -5.42181,-1.062727 -16.494063,3.206249 -16.494064,3.206248 v 14.900444 c 0,14.430688 0.0093,14.952195 0.295638,16.541989 0.677724,3.763097 1.813851,7.543992 3.102693,10.32539 0.832524,1.796635 2.062365,4.131162 2.176318,4.131162 0.04559,0 2.035263,-1.953154 4.421488,-4.340342 z"
id="path1100" />
<path
style="fill:#d40000;fill-opacity:0;stroke-width:0.143184"
d="M 22.451679,62.831985 C 22.040716,62.318926 20.614744,59.414324 19.904415,57.643392 18.969118,55.311589 18.276544,53.003918 17.644871,50.114548 L 17.190978,48.038373 17.14702,32.753436 c -0.02418,-8.406715 0.006,-15.283838 0.06717,-15.282495 0.06112,0.0013 7.450905,-1.423883 16.421746,-3.16717 l 16.310618,-3.169613 5.310229,1.037928 c 2.920626,0.570861 5.383063,1.077918 5.472081,1.126793 0.108366,0.0595 -1.175715,3.968788 -3.885785,11.829987 l -4.047636,11.741123 -8.356483,0.07159 c -6.695958,0.05736 -8.427615,0.110521 -8.714445,0.267501 -0.60381,0.330461 -1.419391,1.193902 -1.576025,1.66851 -0.08176,0.247726 -0.783479,3.851808 -1.559383,8.00907 l -1.410734,7.55866 -4.287731,4.287731 c -3.320136,3.320136 -4.321858,4.245124 -4.438961,4.098932 z"
id="path1139" />
<path
style="fill:#000000;fill-opacity:1;stroke-width:0.143184"
d="m 40.914948,36.977471 c 2.185353,-0.02223 5.761384,-0.02223 7.946736,0 2.185352,0.02223 0.397337,0.04042 -3.973368,0.04042 -4.370704,0 -6.15872,-0.01819 -3.973368,-0.04042 z"
id="path1178" />
<path
style="fill:#aa0000;fill-opacity:1;stroke-width:0.143184"
d="M 21.229102,70.741061 C 20.081432,70.373981 19.32618,69.461642 17.270525,65.959129 14.47136,61.189795 12.54351,55.744636 11.459279,49.545428 L 11.096793,47.472871 V 30.96265 c 0,-13.527886 0.03488,-16.593449 0.193078,-16.970972 0.258937,-0.617909 0.895032,-1.354431 1.391463,-1.611145 0.222726,-0.115176 8.704097,-1.825312 18.847492,-3.8003039 l 18.442537,-3.5908935 7.910402,1.5791153 c 4.350721,0.8685133 8.143647,1.6897975 8.428725,1.8250757 0.961973,0.4564861 1.758687,1.9751714 1.568376,2.9896164 -0.047,0.250541 -2.368442,7.075757 -5.158757,15.167146 -3.44537,9.990924 -5.193245,14.868872 -5.447096,15.201688 -0.205587,0.269539 -0.643902,0.634451 -0.974034,0.810914 -0.596667,0.318933 -0.648799,0.32127 -8.759053,0.392435 l -8.158815,0.07159 -0.470445,2.434135 c -0.258746,1.338774 -0.824052,4.367125 -1.256239,6.729668 -0.432185,2.362543 -0.88633,4.5664 -1.009211,4.897461 -0.177968,0.479472 -1.505767,1.882854 -6.526761,6.898286 -3.466837,3.462998 -6.517904,6.398177 -6.78015,6.52262 -0.601484,0.285423 -1.599068,0.395138 -2.109203,0.231972 z m 5.734389,-11.793889 4.338592,-4.340341 1.438261,-7.722946 c 1.617094,-8.683222 1.602011,-8.634211 2.883301,-9.369051 l 0.745198,-0.427381 8.253581,-0.0013 c 6.783328,-0.0011 8.266754,-0.03567 8.327529,-0.194046 0.04067,-0.105988 1.891336,-5.461638 4.11259,-11.901446 2.549671,-7.391954 3.982955,-11.742017 3.8876,-11.798993 -0.08307,-0.04964 -2.590856,-0.568451 -5.572852,-1.15292 l -5.42181,-1.062727 -16.494063,3.206249 -16.494064,3.206248 v 14.900444 c 0,14.430688 0.0093,14.952195 0.295638,16.541989 0.677724,3.763097 1.813851,7.543992 3.102693,10.32539 0.832524,1.796635 2.062365,4.131162 2.176318,4.131162 0.04559,0 2.035263,-1.953154 4.421488,-4.340342 z"
id="path9953" />
<path
style="fill:#aa0000;fill-opacity:1;stroke-width:0.143184"
d="M 49.327033,94.804719 C 48.714427,94.60513 47.768416,94.086861 45.174685,92.529868 37.917796,88.173624 32.73106,84.14232 27.795635,79.022291 c -1.145161,-1.187994 -2.213316,-2.431436 -2.374239,-2.763859 -0.397355,-0.820826 -0.385516,-1.87334 0.02963,-2.634656 0.211883,-0.388557 2.674852,-2.777493 7.195017,-6.978744 3.780069,-3.513371 7.046744,-6.559837 7.25928,-6.769926 0.367228,-0.363001 0.433179,-0.666405 1.327443,-6.106826 1.040786,-6.331825 1.066013,-6.412508 2.250084,-7.196324 l 0.596675,-0.39498 7.446948,-0.07159 7.446947,-0.07159 6.097509,-16.895762 c 3.353629,-9.292669 6.184278,-17.104473 6.290327,-17.359565 0.271361,-0.652727 1.177566,-1.423686 1.905252,-1.620901 0.55181,-0.149551 1.267997,-0.05876 6.811404,0.863504 3.407116,0.566845 6.423919,1.093902 6.704006,1.171238 0.637026,0.175891 1.521753,0.963097 1.870312,1.664151 0.253992,0.510853 0.264529,1.240952 0.247407,17.14297 -0.01766,16.399934 -0.02182,16.631061 -0.329684,18.327606 -2.58954,14.26993 -9.981308,25.824745 -22.991493,35.940307 -3.466755,2.695441 -7.896512,5.630746 -12.608706,8.354951 -2.064486,1.193517 -2.838015,1.444605 -3.642726,1.182428 z M 52.47709,87.119888 C 59.271254,83.059324 64.365846,79.189404 68.951193,74.605944 76.565106,66.995162 80.844811,58.899716 82.755326,48.494191 l 0.363231,-1.978318 V 32.007709 17.499544 l -0.322165,-0.07398 c -0.957493,-0.219871 -6.829216,-1.157603 -6.884521,-1.099478 -0.03586,0.03769 -2.785127,7.60719 -6.109478,16.821108 -3.324352,9.213917 -6.177636,16.993316 -6.340633,17.287551 -0.353145,0.637482 -1.164147,1.232386 -1.918855,1.407559 -0.300488,0.06975 -3.5908,0.128346 -7.311805,0.130225 -3.721005,0.0019 -6.765464,0.04073 -6.765464,0.08633 0,0.474978 -1.637454,9.59135 -1.804511,10.046442 -0.187845,0.511725 -1.260378,1.573508 -6.797799,6.729668 -3.61612,3.367143 -6.574871,6.194451 -6.575002,6.282908 -3e-4,0.204934 2.512831,2.628316 4.451717,4.292733 1.895466,1.627144 5.431341,4.277542 7.645781,5.731075 2.084726,1.368391 5.558468,3.470987 5.657133,3.424166 0.03938,-0.01869 1.134737,-0.669367 2.434135,-1.44596 z"
id="path10066" />
</svg>

Before

Width:  |  Height:  |  Size: 2.2 KiB

After

Width:  |  Height:  |  Size: 17 KiB

+2 -1
View File
@@ -120,7 +120,8 @@
"pangaea": "Pangaea",
"japan": "Japan and Neighbors",
"betweentwoseas": "Between Two Seas",
"knownworld": "Known World"
"knownworld": "Known World",
"faroeislands": "Faroe Islands"
},
"private_lobby": {
"title": "Join Private Lobby",
File diff suppressed because one or more lines are too long
+43
View File
@@ -0,0 +1,43 @@
{
"name": "Faroe Islands",
"width": 1600,
"height": 2000,
"nations": [
{
"coordinates": [920, 1780],
"name": "Suduroy Region",
"strength": 2,
"flag": "fo"
},
{
"coordinates": [880, 1070],
"name": "Sandoy Region",
"strength": 2,
"flag": "fo"
},
{
"coordinates": [480, 630],
"name": "Vagar Region",
"strength": 1,
"flag": "fo"
},
{
"coordinates": [735, 580],
"name": "Streymoy Region",
"strength": 2,
"flag": "fo"
},
{
"coordinates": [815, 375],
"name": "Eysturoy Region",
"strength": 2,
"flag": "fo"
},
{
"coordinates": [1115, 265],
"name": "Nordoyar Region",
"strength": 2,
"flag": "fo"
}
]
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 663 KiB

File diff suppressed because one or more lines are too long
Binary file not shown.

After

Width:  |  Height:  |  Size: 9.5 KiB

+68 -25
View File
@@ -1,37 +1,80 @@
#!/bin/bash
# Comprehensive setup script for Hetzner server with Docker and Cloudflare R2 configuration
# Comprehensive setup script for Hetzner server with Docker and user setup
# Exit on error
set -e
echo "====================================================="
echo "🚀 STARTING SERVER SETUP"
echo "====================================================="
echo "🔄 Updating system..."
apt update && apt upgrade -y
echo "🐳 Installing Docker..."
# Install Docker using official script
curl -fsSL https://get.docker.com -o get-docker.sh
sh get-docker.sh
systemctl enable --now docker
# Check if Docker is already installed
if command -v docker &> /dev/null; then
echo "Docker is already installed"
else
echo "🐳 Installing Docker..."
# Install Docker using official script
curl -fsSL https://get.docker.com -o get-docker.sh
sh get-docker.sh
systemctl enable --now docker
echo "Docker installed successfully"
fi
# Set up Docker Hub credentials
echo "🔐 Setting up Docker Hub login..."
echo "Enter your Docker Hub username:"
read DOCKER_USERNAME
echo "Enter your Docker Hub password/token:"
read -s DOCKER_PASSWORD
echo $DOCKER_PASSWORD | docker login -u $DOCKER_USERNAME --password-stdin
echo "✅ Docker Hub login configured"
echo "👤 Setting up openfront user..."
# Create openfront user if it doesn't exist
if id "openfront" &>/dev/null; then
echo "User openfront already exists"
else
useradd -m -s /bin/bash openfront
echo "User openfront created"
fi
echo "🔄 Installing Node Exporter..."
# Check if openfront is already in docker group
if groups openfront | grep -q '\bdocker\b'; then
echo "User openfront is already in the docker group"
else
# Add openfront to docker group
usermod -aG docker openfront
echo "Added openfront to docker group"
fi
docker run -d --name node-exporter --restart=unless-stopped \
--net="host" \
--pid="host" \
-v "/:/host:ro,rslave" \
prom/node-exporter:latest \
--path.rootfs=/host
# Create .ssh directory for openfront if it doesn't exist
if [ ! -d "/home/openfront/.ssh" ]; then
mkdir -p /home/openfront/.ssh
chmod 700 /home/openfront/.ssh
echo "Created .ssh directory for openfront"
fi
echo "node-exporter installed"
# Copy SSH keys from root if they exist and haven't been copied yet
if [ -f /root/.ssh/authorized_keys ] && [ ! -f /home/openfront/.ssh/authorized_keys ]; then
cp /root/.ssh/authorized_keys /home/openfront/.ssh/
chmod 600 /home/openfront/.ssh/authorized_keys
echo "SSH keys copied from root to openfront"
fi
echo "🎉 Setup complete! You can find helpful Docker and R2 commands in ~/docker-commands.sh"
echo "Test your R2 connection: aws s3 ls --profile r2"
# Check if node-exporter container already exists
if docker ps -a | grep -q "node-exporter"; then
echo "Node Exporter is already installed"
else
echo "🔄 Installing Node Exporter..."
docker run -d --name node-exporter --restart=unless-stopped \
--net="host" \
--pid="host" \
-v "/:/host:ro,rslave" \
prom/node-exporter:latest \
--path.rootfs=/host
echo "Node Exporter installed successfully"
fi
# Set proper ownership for openfront's home directory
chown -R openfront:openfront /home/openfront
echo "Set proper ownership for openfront's home directory"
echo "====================================================="
echo "🎉 SETUP COMPLETE!"
echo "====================================================="
echo "The openfront user has been set up and has Docker permissions."
echo "You can now deploy using the openfront user."
echo "====================================================="
+26
View File
@@ -12,6 +12,7 @@ import { createGameRecord } from "../core/Util";
import { ServerConfig } from "../core/configuration/Config";
import { getConfig } from "../core/configuration/ConfigLoader";
import { Team, UnitType } from "../core/game/Game";
import { TileRef } from "../core/game/GameMap";
import {
ErrorUpdate,
GameUpdateType,
@@ -28,6 +29,7 @@ import { endGame, startGame, startTime } from "./LocalPersistantStats";
import { getPersistentIDFromCookie } from "./Main";
import {
SendAttackIntentEvent,
SendBoatAttackIntentEvent,
SendHashEvent,
SendSpawnIntentEvent,
Transport,
@@ -359,6 +361,18 @@ export class ClientGameRunner {
this.myPlayer.troops() * this.renderer.uiState.attackRatio,
),
);
} else if (
actions.canBoat !== false &&
this.shouldBoat(tile, actions.canBoat) &&
this.gameView.isLand(tile)
) {
this.eventBus.emit(
new SendBoatAttackIntentEvent(
this.gameView.owner(tile).id(),
cell,
this.myPlayer.troops() * this.renderer.uiState.attackRatio,
),
);
}
const owner = this.gameView.owner(tile);
@@ -370,6 +384,18 @@ export class ClientGameRunner {
});
}
private shouldBoat(tile: TileRef, src: TileRef) {
// TODO: Global enable flag
// TODO: Global limit autoboat to nearby shore flag
// if (!enableAutoBoat) return false;
// if (!limitAutoBoatNear) return true;
const distanceSquared = this.gameView.euclideanDistSquared(tile, src);
const limit = 100;
const limitSquared = limit * limit;
if (distanceSquared > limitSquared) return false;
return true;
}
private onMouseMove(event: MouseMoveEvent) {
this.lastMousePosition = { x: event.x, y: event.y };
this.checkTileUnderCursor();
+130
View File
@@ -0,0 +1,130 @@
export class MultiTabDetector {
private focusChanges: number[] = [];
private readonly maxFocusChanges: number = 10;
private readonly timeWindow: number = 60_000;
private readonly punishmentDelays: number[] = [
2_000, 3_000, 5_000, 10_000, 30_000, 60_000,
];
private lastFocusChangeTime: number = 0;
private isPunished: boolean = false;
private isMonitoring: boolean = false;
private startPenaltyCallback?: (duration: number) => void;
private numPunishmentsGiven = 0;
/**
* Start monitoring for multi-tabbing behavior
*
* @param startPenalty Callback function when punishment starts
*/
public startMonitoring(startPenalty: (duration: number) => void): void {
if (this.isMonitoring) return;
this.isMonitoring = true;
this.startPenaltyCallback = startPenalty;
// Event listeners for window focus/blur
window.addEventListener("blur", this.handleFocusChange.bind(this));
window.addEventListener("focus", this.handleFocusChange.bind(this));
// Also track visibility changes for tab switching
document.addEventListener(
"visibilitychange",
this.handleVisibilityChange.bind(this),
);
}
public stopMonitoring(): void {
if (!this.isMonitoring) return;
this.isMonitoring = false;
// Remove event listeners
window.removeEventListener("blur", this.handleFocusChange.bind(this));
window.removeEventListener("focus", this.handleFocusChange.bind(this));
document.removeEventListener(
"visibilitychange",
this.handleVisibilityChange.bind(this),
);
// Clear data
this.focusChanges = [];
this.isPunished = false;
}
private handleFocusChange(): void {
const currentTime = Date.now();
this.recordFocusChange(currentTime);
// Check for multi-tabbing when focus is gained
if (document.hasFocus() && !this.isPunished) {
this.checkForMultiTabbing(currentTime);
}
}
private handleVisibilityChange(): void {
const currentTime = Date.now();
// Record and check regardless of current focus state
this.recordFocusChange(currentTime);
// Only check when tab becomes visible
if (document.visibilityState === "visible" && !this.isPunished) {
this.checkForMultiTabbing(currentTime);
}
}
private recordFocusChange(timestamp: number): void {
if (Math.abs(this.lastFocusChangeTime - timestamp) < 100) {
// Don't count multiple triggers at same time
return;
}
this.focusChanges.push(timestamp);
console.log(`pushing focus change at ${timestamp}`);
this.lastFocusChangeTime = timestamp;
// Keep only recent changes
if (this.focusChanges.length > this.maxFocusChanges) {
this.focusChanges.shift();
}
}
private checkForMultiTabbing(currentTime: number): void {
// Only if we have enough data points
if (this.focusChanges.length >= this.maxFocusChanges) {
const oldestChange = this.focusChanges[0];
const timeSpan = currentTime - oldestChange;
// If changes happened within detection window
if (timeSpan <= this.timeWindow) {
this.applyPunishment();
}
}
}
private applyPunishment(): void {
// Prevent multiple punishments
if (this.isPunished) return;
this.isPunished = true;
let punishmentDelay = 0;
if (this.numPunishmentsGiven >= this.punishmentDelays.length) {
punishmentDelay = this.punishmentDelays[this.punishmentDelays.length - 1];
} else {
punishmentDelay = this.punishmentDelays[this.numPunishmentsGiven];
}
this.numPunishmentsGiven++;
// Call the start penalty callback
if (this.startPenaltyCallback) {
this.startPenaltyCallback(punishmentDelay);
}
// Remove penalty after delay
setTimeout(() => {
this.isPunished = false;
}, punishmentDelay);
}
}
+1
View File
@@ -23,6 +23,7 @@ export const MapDescription: Record<keyof typeof GameMapType, string> = {
Japan: "Japan",
BetweenTwoSeas: "Between Two Seas",
KnownWorld: "Known World",
FaroeIslands: "Faroe Islands",
};
@customElement("map-display")
+10
View File
@@ -12,6 +12,7 @@ import { EmojiTable } from "./layers/EmojiTable";
import { EventsDisplay } from "./layers/EventsDisplay";
import { Layer } from "./layers/Layer";
import { Leaderboard } from "./layers/Leaderboard";
import { MultiTabModal } from "./layers/MultiTabModal";
import { NameLayer } from "./layers/NameLayer";
import { OptionsMenu } from "./layers/OptionsMenu";
import { PlayerInfoOverlay } from "./layers/PlayerInfoOverlay";
@@ -125,6 +126,14 @@ export function createRenderer(
playerPanel.eventBus = eventBus;
playerPanel.emojiTable = emojiTable;
const multiTabModal = document.querySelector(
"multi-tab-modal",
) as MultiTabModal;
if (!(multiTabModal instanceof MultiTabModal)) {
console.error("multi-tab modal not found");
}
multiTabModal.game = game;
const layers: Layer[] = [
new TerrainLayer(game, transformHandler),
new TerritoryLayer(game, eventBus),
@@ -153,6 +162,7 @@ export function createRenderer(
optionsMenu,
topBar,
playerPanel,
multiTabModal,
];
return new GameRenderer(
+2 -1
View File
@@ -76,10 +76,11 @@ export const getColoredSprite = (
unit: UnitView,
theme: Theme,
customTerritoryColor?: Colord,
customBorderColor?: Colord,
): HTMLCanvasElement => {
const owner = unit.owner();
const territoryColor = customTerritoryColor ?? theme.territoryColor(owner);
const borderColor = theme.borderColor(owner);
const borderColor = customBorderColor ?? theme.borderColor(owner);
const spawnHighlightColor = theme.spawnHighlightColor();
const colorKey = customTerritoryColor
? customTerritoryColor.toRgbString()
+131
View File
@@ -0,0 +1,131 @@
import { LitElement, html } from "lit";
import { customElement, property, state } from "lit/decorators.js";
import { GameType } from "../../../core/game/Game";
import { GameView } from "../../../core/game/GameView";
import { MultiTabDetector } from "../../MultiTabDetector";
import { translateText } from "../../Utils";
import { Layer } from "./Layer";
@customElement("multi-tab-modal")
export class MultiTabModal extends LitElement implements Layer {
public game: GameView;
private detector: MultiTabDetector;
@property({ type: Number }) duration: number = 5000;
@state() private countdown: number = 5;
@state() private isVisible: boolean = false;
private intervalId?: number;
// Disable shadow DOM to allow Tailwind classes to work
createRenderRoot() {
return this;
}
tick() {
if (
this.game.inSpawnPhase() ||
this.game.config().gameConfig().gameType == GameType.Singleplayer
) {
return;
}
if (!this.detector) {
this.detector = new MultiTabDetector();
this.detector.startMonitoring((duration: number) => {
this.show(duration);
});
}
}
// Show the modal with penalty information
public show(duration: number): void {
if (!this.game.myPlayer()?.isAlive()) {
return;
}
this.duration = duration;
this.countdown = Math.ceil(duration / 1000);
this.isVisible = true;
// Start countdown timer
this.intervalId = window.setInterval(() => {
this.countdown--;
if (this.countdown <= 0) {
this.hide();
}
}, 1000);
this.requestUpdate();
}
// Hide the modal
public hide(): void {
this.isVisible = false;
if (this.intervalId) {
window.clearInterval(this.intervalId);
this.intervalId = undefined;
}
// Dispatch event when modal is closed
this.dispatchEvent(
new CustomEvent("penalty-complete", {
bubbles: true,
composed: true,
}),
);
this.requestUpdate();
}
disconnectedCallback() {
super.disconnectedCallback();
if (this.intervalId) {
window.clearInterval(this.intervalId);
}
}
render() {
if (!this.isVisible) {
return html``;
}
return html`
<div
class="fixed inset-0 z-50 overflow-auto bg-red-500/20 flex items-center justify-center"
>
<div
class="relative p-6 bg-white dark:bg-gray-800 rounded-xl shadow-xl max-w-md w-full m-4 transition-all transform"
>
<h2 class="text-2xl font-bold mb-4 text-red-600 dark:text-red-400">
${translateText("multi_tab.warning")}
</h2>
<p class="mb-4 text-gray-800 dark:text-gray-200">
${translateText("multi_tab.detected")}
</p>
<p class="mb-4 text-gray-800 dark:text-gray-200">
${translateText("multi_tab.please_wait")}
<span class="font-bold text-xl">${this.countdown}</span>
${translateText("multi_tab.seconds")}
</p>
<div
class="w-full bg-gray-200 dark:bg-gray-700 rounded-full h-2.5 mb-4"
>
<div
class="bg-red-600 dark:bg-red-500 h-2.5 rounded-full transition-all duration-1000 ease-linear"
style="width: ${(this.countdown / (this.duration / 1000)) * 100}%"
></div>
</div>
<p class="text-sm text-gray-600 dark:text-gray-400">
${translateText("multi_tab.explanation")}
</p>
</div>
</div>
`;
}
}
+8 -2
View File
@@ -145,8 +145,14 @@ export class PlayerPanel extends LitElement implements Layer {
this.eventBus.on(MouseUpEvent, (e: MouseEvent) => this.hide());
}
tick() {
this.requestUpdate();
async tick() {
if (this.isVisible && this.tile) {
const myPlayer = this.g.myPlayer();
if (myPlayer !== null && myPlayer.isAlive()) {
this.actions = await myPlayer.actions(this.tile);
this.requestUpdate();
}
}
}
getTotalNukesSent(otherId: PlayerID): number {
+34 -14
View File
@@ -8,7 +8,7 @@ import swordIcon from "../../../../resources/images/SwordIconWhite.svg";
import traitorIcon from "../../../../resources/images/TraitorIconWhite.svg";
import { consolex } from "../../../core/Consolex";
import { EventBus } from "../../../core/EventBus";
import { Cell, PlayerActions } from "../../../core/game/Game";
import { Cell, PlayerActions, TerraNullius } from "../../../core/game/Game";
import { TileRef } from "../../../core/game/GameMap";
import { GameView, PlayerView } from "../../../core/game/GameView";
import { ClientID } from "../../../core/Schemas";
@@ -44,6 +44,7 @@ export class RadialMenu implements Layer {
private clickedCell: Cell | null = null;
private lastClosed: number = 0;
private originalTileOwner: PlayerView | TerraNullius;
private menuElement: d3.Selection<HTMLDivElement, unknown, null, undefined>;
private isVisible: boolean = false;
private readonly menuItems = new Map([
@@ -267,8 +268,26 @@ export class RadialMenu implements Layer {
.style("pointer-events", "none");
}
tick() {
// Update logic if needed
async tick() {
// Only update when menu is visible
if (!this.isVisible || this.clickedCell === null) return;
const myPlayer = this.g.myPlayer();
if (myPlayer === null || !myPlayer.isAlive()) return;
const tile = this.g.ref(this.clickedCell.x, this.clickedCell.y);
if (this.originalTileOwner.isPlayer()) {
if (this.g.owner(tile) != this.originalTileOwner) {
this.closeMenu();
return;
}
} else {
if (this.g.owner(tile).isPlayer() || this.g.owner(tile) == myPlayer) {
this.closeMenu();
return;
}
}
const actions = await myPlayer.actions(tile);
this.disableAllButtons();
this.handlePlayerActions(myPlayer, actions, tile);
}
renderLayer(context: CanvasRenderingContext2D) {
@@ -291,12 +310,7 @@ export class RadialMenu implements Layer {
} else {
this.showRadialMenu(event.x, event.y);
}
this.enableCenterButton(false);
for (const item of this.menuItems.values()) {
item.disabled = true;
this.updateMenuItemState(item);
}
this.disableAllButtons();
this.clickedCell = this.transformHandler.screenToWorldCoordinates(
event.x,
event.y,
@@ -305,7 +319,7 @@ export class RadialMenu implements Layer {
return;
}
const tile = this.g.ref(this.clickedCell.x, this.clickedCell.y);
this.originalTileOwner = this.g.owner(tile);
if (this.g.inSpawnPhase()) {
if (this.g.isLand(tile) && !this.g.hasOwner(tile)) {
this.enableCenterButton(true);
@@ -313,10 +327,8 @@ export class RadialMenu implements Layer {
return;
}
const myPlayer = this.g
.playerViews()
.find((p) => p.clientID() == this.clientID);
if (!myPlayer) {
const myPlayer = this.g.myPlayer();
if (myPlayer === null) {
consolex.warn("my player not found");
return;
}
@@ -430,6 +442,14 @@ export class RadialMenu implements Layer {
this.hideRadialMenu();
}
private disableAllButtons() {
this.enableCenterButton(false);
for (const item of this.menuItems.values()) {
item.disabled = true;
this.updateMenuItemState(item);
}
}
private activateMenuElement(
slot: Slot,
color: string,
+35 -3
View File
@@ -203,6 +203,18 @@ export class UnitLayer implements Layer {
?.[GameUpdateType.Unit]?.forEach((unit) => {
this.onUnitEvent(this.game.unit(unit.id));
});
this.boatToTrail.forEach((trail, unit) => {
for (const t of trail) {
this.paintCell(
this.game.x(t),
this.game.y(t),
this.relationship(unit),
this.theme.territoryColor(unit.owner()),
150,
this.transportShipTrailContext,
);
}
});
}
private relationship(unit: UnitView): Relationship {
@@ -365,8 +377,6 @@ export class UnitLayer implements Layer {
}
private handleTradeShipEvent(unit: UnitView) {
const rel = this.relationship(unit);
// Clear previous area
for (const t of this.game.bfs(
unit.lastTile(),
@@ -479,7 +489,29 @@ export class UnitLayer implements Layer {
const x = this.game.x(unit.tile());
const y = this.game.y(unit.tile());
const sprite = getColoredSprite(unit, this.theme, customTerritoryColor);
let alternateViewColor = null;
if (this.alternateView) {
const rel = this.relationship(unit);
switch (rel) {
case Relationship.Self:
alternateViewColor = this.theme.selfColor();
break;
case Relationship.Ally:
alternateViewColor = this.theme.allyColor();
break;
case Relationship.Enemy:
alternateViewColor = this.theme.enemyColor();
break;
}
}
const sprite = getColoredSprite(
unit,
this.theme,
alternateViewColor ?? customTerritoryColor,
alternateViewColor,
);
this.context.drawImage(
sprite,
+1 -24
View File
@@ -160,30 +160,7 @@ export class WinModal extends LitElement implements Layer {
innerHtml() {
return html`
<div style="text-align: center; margin: 15px 0; line-height: 1.5;">
<p>
<span style="color: red;">Time's running out!</span> <br />
I need your support to continue working on OpenFront full-time. Please
donate now to keep the updates, new features, and improvements coming.
</p>
<a
href="https://patreon.com/OpenFront"
target="_blank"
rel="noopener noreferrer"
style="
display: inline-block;
padding: 10px 20px;
background-color: #FF424D;
color: white;
text-decoration: none;
border-radius: 5px;
font-weight: bold;
transition: background-color 0.2s;
"
>
Support on Patreon
</a>
</div>
<div style="text-align: center; margin: 15px 0; line-height: 1.5;"></div>
`;
}
+10 -4
View File
@@ -48,8 +48,10 @@
.left-gutter-ad {
position: fixed;
left: 0;
top: 200px; /* Changed from top: 50% */
transform: none; /* Removed translateY(-50%) since we don't need to center anymore */
top: 200px;
/* Changed from top: 50% */
transform: none;
/* Removed translateY(-50%) since we don't need to center anymore */
z-index: 40;
width: 300px;
height: 600px;
@@ -68,8 +70,10 @@
.right-gutter-ad {
position: fixed;
right: 0;
top: 200px; /* Changed from top: 50% */
transform: none; /* Removed translateY(-50%) since we don't need to center anymore */
top: 200px;
/* Changed from top: 50% */
transform: none;
/* Removed translateY(-50%) since we don't need to center anymore */
z-index: 40;
width: 300px;
height: 600px;
@@ -137,6 +141,7 @@
gtag("config", "G-WQGQQ8RDN4");
</script>
</head>
<body
class="h-full select-none font-sans min-h-screen bg-opacity-0 bg-cover bg-center bg-fixed transition-opacity duration-300 ease-in-out flex flex-col"
>
@@ -353,6 +358,7 @@
<help-modal></help-modal>
<dark-mode-button></dark-mode-button>
<user-setting></user-setting>
<multi-tab-modal></multi-tab-modal>
<div
id="language-modal"
class="fixed inset-0 bg-black bg-opacity-50 z-50 hidden flex justify-center items-center"
+3
View File
@@ -5,6 +5,7 @@ import betweenTwoSeas from "../../../resources/maps/BetweenTwoSeasThumb.webp";
import blackSea from "../../../resources/maps/BlackSeaThumb.webp";
import britannia from "../../../resources/maps/BritanniaThumb.webp";
import europe from "../../../resources/maps/EuropeThumb.webp";
import faroeislands from "../../../resources/maps/FaroeIslandsThumb.webp";
import gatewayToTheAtlantic from "../../../resources/maps/GatewayToTheAtlanticThumb.webp";
import iceland from "../../../resources/maps/IcelandThumb.webp";
import japan from "../../../resources/maps/JapanThumb.webp";
@@ -57,6 +58,8 @@ export function getMapsImage(map: GameMapType): string {
return betweenTwoSeas;
case GameMapType.KnownWorld:
return knownworld;
case GameMapType.FaroeIslands:
return faroeislands;
default:
return "";
}
+2
View File
@@ -52,6 +52,7 @@ export interface NukeMagnitude {
export interface Config {
samHittingChance(): number;
samWarheadHittingChance(): number;
spawnImmunityDuration(): Tick;
serverConfig(): ServerConfig;
gameConfig(): GameConfig;
@@ -118,6 +119,7 @@ export interface Config {
difficultyModifier(difficulty: Difficulty): number;
// 0-1
traitorDefenseDebuff(): number;
traitorDuration(): number;
nukeMagnitudes(unitType: UnitType): NukeMagnitude;
defaultNukeSpeed(): number;
nukeDeathFactor(humans: number, tilesOwned: number): number;
+11 -3
View File
@@ -34,7 +34,7 @@ export abstract class DefaultServerConfig implements ServerConfig {
return process.env.GIT_COMMIT;
}
r2Endpoint(): string {
return process.env.R2_ENDPOINT;
return `https://${process.env.R2_ACCOUNT_ID}.r2.cloudflarestorage.com`;
}
r2AccessKey(): string {
return process.env.R2_ACCESS_KEY;
@@ -89,6 +89,7 @@ export abstract class DefaultServerConfig implements ServerConfig {
GameMapType.Mars,
GameMapType.Oceania,
GameMapType.Japan, // Japan at this level because its 2/3 water
GameMapType.FaroeIslands,
].includes(map)
) {
return Math.random() < 0.2 ? 70 : 40;
@@ -135,8 +136,15 @@ export class DefaultConfig implements Config {
return 0.8;
}
samWarheadHittingChance(): number {
return 0.5;
}
traitorDefenseDebuff(): number {
return 0.8;
return 0.5;
}
traitorDuration(): number {
return 30 * 10; // 30 seconds
}
spawnImmunityDuration(): Tick {
return 5 * 10;
@@ -325,7 +333,7 @@ export class DefaultConfig implements Config {
p.type() == PlayerType.Human && this.infiniteGold()
? 0
: Math.min(
1_500_000 * 3,
3_000_000,
(p.unitsIncludingConstruction(UnitType.SAMLauncher).length +
1) *
1_500_000,
+8
View File
@@ -24,6 +24,14 @@ export class DevServerConfig extends DefaultServerConfig {
return Math.random() < 0.5 ? 2 : 3;
}
samWarheadHittingChance(): number {
return 1;
}
samHittingChance(): number {
return 1;
}
discordRedirectURI(): string {
return "http://localhost:3000/auth/callback";
}
+36 -116
View File
@@ -1,15 +1,7 @@
import {
Execution,
Game,
Player,
PlayerType,
Relation,
TerraNullius,
Tick,
} from "../game/Game";
import { Execution, Game, Player } from "../game/Game";
import { PseudoRandom } from "../PseudoRandom";
import { simpleHash } from "../Util";
import { AttackExecution } from "./AttackExecution";
import { BotBehavior } from "./utils/BotBehavior";
export class BotExecution implements Execution {
private active = true;
@@ -17,8 +9,7 @@ export class BotExecution implements Execution {
private mg: Game;
private neighborsTerraNullius = true;
private enemy: Player | null = null;
private lastEnemyUpdateTick: Tick;
private behavior: BotBehavior | null = null;
private attackRate: number;
private attackTick: number;
private triggerRatio: number;
@@ -36,7 +27,7 @@ export class BotExecution implements Execution {
return false;
}
init(mg: Game, ticks: number) {
init(mg: Game) {
this.mg = mg;
this.bot.setTargetTroopRatio(0.7);
}
@@ -49,118 +40,47 @@ export class BotExecution implements Execution {
return;
}
this.handleAllianceRequests();
if (this.behavior === null) {
this.behavior = new BotBehavior(
this.random,
this.mg,
this.bot,
this.triggerRatio,
this.reserveRatio,
);
}
this.behavior.handleAllianceRequests();
this.maybeAttack();
}
private handleAllianceRequests() {
this.bot.incomingAllianceRequests().forEach((ar) => {
const requestorIsMuchLarger =
ar.requestor().numTilesOwned() > this.bot.numTilesOwned() * 3;
if (
ar.requestor().isTraitor() ||
this.bot.relation(ar.requestor()) < Relation.Neutral ||
(!requestorIsMuchLarger && ar.requestor().alliances().length >= 3)
) {
ar.reject();
} else {
ar.accept();
}
});
}
private maybeAttack() {
const traitors = this.bot
.neighbors()
.filter((n) => n.isPlayer() && n.isTraitor()) as Player[];
if (traitors.length > 0) {
const toAttack = this.random.randElement(traitors);
const odds = this.bot.isFriendly(toAttack) ? 6 : 3;
if (this.random.chance(odds)) {
this.behavior.sendAttack(toAttack);
return;
}
}
if (this.neighborsTerraNullius) {
for (const b of this.bot.borderTiles()) {
for (const n of this.mg.neighbors(b)) {
if (!this.mg.hasOwner(n) && this.mg.isLand(n)) {
this.sendAttack(this.mg.terraNullius());
return;
}
}
if (this.bot.sharesBorderWith(this.mg.terraNullius())) {
this.behavior.sendAttack(this.mg.terraNullius());
return;
}
this.neighborsTerraNullius = false;
}
if (this.mg.ticks() - this.lastEnemyUpdateTick > 100) {
this.enemy = null;
}
// Switch enemies if we're under attack
const incomingAttacks = this.bot.incomingAttacks();
if (incomingAttacks.length > 0) {
this.enemy = incomingAttacks
.sort((a, b) => b.troops() - a.troops())[0]
.attacker();
this.lastEnemyUpdateTick = this.mg.ticks();
}
if (this.enemy === null) {
// Save up troops until we reach the trigger ratio
const maxPop = this.mg.config().maxPopulation(this.bot);
const ratio = this.bot.population() / maxPop;
if (ratio < this.triggerRatio) return;
// Choose a new enemy randomly
const border = Array.from(this.bot.borderTiles())
.flatMap((t) => this.mg.neighbors(t))
.filter(
(t) => this.mg.isLand(t) && this.mg.ownerID(t) != this.bot.smallID(),
);
if (border.length > 0) {
const toAttack = this.random.randElement(border);
const owner = this.mg.owner(toAttack);
if (!owner.isPlayer()) {
this.sendAttack(this.mg.terraNullius());
this.neighborsTerraNullius = true;
return;
}
this.enemy = owner;
this.lastEnemyUpdateTick = this.mg.ticks();
}
// Select a traitor as an enemy
const traitors = this.bot
.neighbors()
.filter((n) => n.isPlayer() && n.isTraitor()) as Player[];
if (traitors.length > 0) {
const toAttack = this.random.randElement(traitors);
const odds = this.bot.isFriendly(toAttack) ? 6 : 3;
if (this.random.chance(odds)) {
this.enemy = toAttack;
this.lastEnemyUpdateTick = this.mg.ticks();
}
}
}
if (this.enemy) {
if (this.bot.isFriendly(this.enemy) && this.random.chance(50)) {
this.enemy = null;
return;
}
if (this.enemy.type() == PlayerType.FakeHuman) {
if (!this.random.chance(2)) {
return;
}
}
this.sendAttack(this.enemy);
}
}
private sendAttack(toAttack: Player | TerraNullius) {
if (toAttack.isPlayer() && this.bot.isOnSameTeam(toAttack)) return;
const maxPop = this.mg.config().maxPopulation(this.bot);
const max = maxPop * this.bot.targetTroopRatio();
const target = max * this.reserveRatio;
const troops = this.bot.troops() - target;
if (troops < 1) return;
this.mg.addExecution(
new AttackExecution(
troops,
this.bot.id(),
toAttack.isPlayer() ? toAttack.id() : null,
),
);
this.behavior.forgetOldEnemies();
this.behavior.checkIncomingAttacks();
const enemy = this.behavior.selectRandomEnemy();
if (!enemy) return;
if (!this.bot.sharesBorderWith(enemy)) return;
this.behavior.sendAttack(enemy);
}
isActive(): boolean {
+54 -139
View File
@@ -1,6 +1,5 @@
import { consolex } from "../Consolex";
import {
AllianceRequest,
Cell,
Difficulty,
Execution,
@@ -11,7 +10,6 @@ import {
PlayerType,
Relation,
TerrainType,
TerraNullius,
Tick,
Unit,
UnitType,
@@ -20,30 +18,28 @@ import { euclDistFN, manhattanDistFN, TileRef } from "../game/GameMap";
import { PseudoRandom } from "../PseudoRandom";
import { GameID } from "../Schemas";
import { calculateBoundingBox, simpleHash } from "../Util";
import { AllianceRequestReplyExecution } from "./alliance/AllianceRequestReplyExecution";
import { AttackExecution } from "./AttackExecution";
import { ConstructionExecution } from "./ConstructionExecution";
import { EmojiExecution } from "./EmojiExecution";
import { NukeExecution } from "./NukeExecution";
import { SpawnExecution } from "./SpawnExecution";
import { TransportShipExecution } from "./TransportShipExecution";
import { closestTwoTiles } from "./Util";
import { BotBehavior } from "./utils/BotBehavior";
export class FakeHumanExecution implements Execution {
private firstMove = true;
private active = true;
private random: PseudoRandom;
private behavior: BotBehavior | null = null;
private mg: Game;
private player: Player = null;
private enemy: Player | null = null;
private attackRate: number;
private attackTick: number;
private triggerRatio: number;
private reserveRatio: number;
private lastEnemyUpdateTick: number = 0;
private lastEmojiSent = new Map<Player, Tick>();
private lastNukeSent: [Tick, TileRef][] = [];
private embargoMalusApplied = new Set<PlayerID>();
@@ -61,7 +57,7 @@ export class FakeHumanExecution implements Execution {
this.reserveRatio = this.random.nextInt(30, 60) / 100;
}
init(mg: Game, ticks: number) {
init(mg: Game) {
this.mg = mg;
if (this.random.chance(10)) {
// this.isTraitor = true
@@ -127,16 +123,29 @@ export class FakeHumanExecution implements Execution {
return;
}
}
if (this.firstMove) {
this.firstMove = false;
this.sendAttack(this.mg.terraNullius());
return;
}
if (!this.player.isAlive()) {
this.active = false;
return;
}
if (this.behavior === null) {
// Player is unavailable during init()
this.behavior = new BotBehavior(
this.random,
this.mg,
this.player,
this.triggerRatio,
this.reserveRatio,
);
}
if (this.firstMove) {
this.firstMove = false;
this.behavior.sendAttack(this.mg.terraNullius());
return;
}
if (
this.player.troops() > 100_000 &&
this.player.targetTroopRatio() > 0.7
@@ -145,7 +154,7 @@ export class FakeHumanExecution implements Execution {
}
this.updateRelationsFromEmbargos();
this.handleAllianceRequests();
this.behavior.handleAllianceRequests();
this.handleEnemies();
this.handleUnits();
this.handleEmbargoesToHostileNations();
@@ -159,14 +168,6 @@ export class FakeHumanExecution implements Execution {
(t) => this.mg.isLand(t) && this.mg.ownerID(t) != this.player.smallID(),
);
const enemiesWithTN = enemyborder.map((t) =>
this.mg.playerBySmallID(this.mg.ownerID(t)),
);
if (enemiesWithTN.filter((o) => !o.isPlayer()).length > 0) {
this.sendAttack(this.mg.terraNullius());
return;
}
if (enemyborder.length == 0) {
if (this.random.chance(10)) {
this.sendBoatRandomly();
@@ -178,17 +179,24 @@ export class FakeHumanExecution implements Execution {
return;
}
const enemiesWithTN = enemyborder.map((t) =>
this.mg.playerBySmallID(this.mg.ownerID(t)),
);
if (enemiesWithTN.filter((o) => !o.isPlayer()).length > 0) {
this.behavior.sendAttack(this.mg.terraNullius());
return;
}
const enemies = enemiesWithTN
.filter((o) => o.isPlayer())
.sort((a, b) => a.troops() - b.troops());
// 5% chance to send a random alliance request
if (this.random.chance(20)) {
for (const toAlly of enemies) {
if (this.player.canSendAllianceRequest(toAlly)) {
this.player.createAllianceRequest(toAlly);
return;
}
const toAlly = this.random.randElement(enemies);
if (this.player.canSendAllianceRequest(toAlly)) {
this.player.createAllianceRequest(toAlly);
return;
}
}
@@ -197,7 +205,7 @@ export class FakeHumanExecution implements Execution {
? enemies[0]
: this.random.randElement(enemies);
if (this.shouldAttack(toAttack)) {
this.sendAttack(toAttack);
this.behavior.sendAttack(toAttack);
}
}
@@ -234,77 +242,29 @@ export class FakeHumanExecution implements Execution {
}
handleEnemies() {
if (this.mg.ticks() - this.lastEnemyUpdateTick > 100) {
this.enemy = null;
}
// Switch enemies if we're under attack
const incomingAttacks = this.player.incomingAttacks();
if (incomingAttacks.length > 0) {
this.enemy = incomingAttacks
.sort((a, b) => b.troops() - a.troops())[0]
.attacker();
this.lastEnemyUpdateTick = this.mg.ticks();
}
// Attack allies' targets
outer: for (const ally of this.player.allies()) {
if (this.player.relation(ally) < Relation.Friendly) continue;
for (const target of ally.targets()) {
if (target === this.player) continue;
if (this.player.isAlliedWith(target)) continue;
this.player.updateRelation(ally, -20);
this.enemy = target;
this.lastEnemyUpdateTick = this.mg.ticks();
if (ally.type() == PlayerType.Human) {
this.mg.addExecution(
new EmojiExecution(this.player.id(), ally.id(), "👍"),
);
}
break outer;
}
}
if (this.enemy === null) {
// Save up troops until we reach the trigger ratio
const maxPop = this.mg.config().maxPopulation(this.player);
const ratio = this.player.population() / maxPop;
if (ratio < this.triggerRatio) return;
// Choose a new enemy
const mostHated = this.player.allRelationsSorted()[0] ?? null;
if (mostHated != null && mostHated.relation == Relation.Hostile) {
this.enemy = mostHated.player;
this.lastEnemyUpdateTick = this.mg.ticks();
}
}
if (this.enemy) {
if (this.player.isFriendly(this.enemy)) {
this.enemy = null;
return;
}
this.maybeSendEmoji();
this.maybeSendNuke(this.enemy);
if (this.player.sharesBorderWith(this.enemy)) {
this.sendAttack(this.enemy);
} else {
this.maybeSendBoatAttack(this.enemy);
}
return;
this.behavior.forgetOldEnemies();
this.behavior.checkIncomingAttacks();
this.behavior.assistAllies();
const enemy = this.behavior.selectEnemy();
if (!enemy) return;
this.maybeSendEmoji(enemy);
this.maybeSendNuke(enemy);
if (this.player.sharesBorderWith(enemy)) {
this.behavior.sendAttack(enemy);
} else {
this.maybeSendBoatAttack(enemy);
}
}
private maybeSendEmoji() {
if (this.enemy.type() != PlayerType.Human) return;
const lastSent = this.lastEmojiSent.get(this.enemy) ?? -300;
private maybeSendEmoji(enemy: Player) {
if (enemy.type() != PlayerType.Human) return;
const lastSent = this.lastEmojiSent.get(enemy) ?? -300;
if (this.mg.ticks() - lastSent <= 300) return;
this.lastEmojiSent.set(this.enemy, this.mg.ticks());
this.lastEmojiSent.set(enemy, this.mg.ticks());
this.mg.addExecution(
new EmojiExecution(
this.player.id(),
this.enemy.id(),
enemy.id(),
this.random.randElement(["🤡", "😡"]),
),
);
@@ -315,6 +275,7 @@ export class FakeHumanExecution implements Execution {
if (
silos.length == 0 ||
this.player.gold() < this.cost(UnitType.AtomBomb) ||
other.type() == PlayerType.Bot ||
this.player.isOnSameTeam(other)
) {
return;
@@ -402,7 +363,8 @@ export class FakeHumanExecution implements Execution {
// Prefer tiles that are closer to a silo
const siloTiles = silos.map((u) => u.tile());
const { x: closestSilo } = closestTwoTiles(this.mg, siloTiles, [tile]);
const distanceToClosestSilo = this.mg.euclideanDist(tile, closestSilo);
const distanceSquared = this.mg.euclideanDistSquared(tile, closestSilo);
const distanceToClosestSilo = Math.sqrt(distanceSquared);
tileValue -= distanceToClosestSilo * 30;
// Don't target near recent targets
@@ -558,36 +520,6 @@ export class FakeHumanExecution implements Execution {
return this.mg.unitInfo(type).cost(this.player);
}
private handleAllianceRequests() {
for (const req of this.player.incomingAllianceRequests()) {
if (req.requestor().isTraitor()) {
this.replyToAllianceRequest(req, false);
continue;
}
if (this.player.relation(req.requestor()) < Relation.Neutral) {
this.replyToAllianceRequest(req, false);
continue;
}
const requestorIsMuchLarger =
req.requestor().numTilesOwned() > this.player.numTilesOwned() * 3;
if (!requestorIsMuchLarger && req.requestor().alliances().length >= 3) {
this.replyToAllianceRequest(req, false);
continue;
}
this.replyToAllianceRequest(req, true);
}
}
private replyToAllianceRequest(req: AllianceRequest, accept: boolean): void {
this.mg.addExecution(
new AllianceRequestReplyExecution(
req.requestor().id(),
this.player.id(),
accept,
),
);
}
sendBoatRandomly() {
const oceanShore = Array.from(this.player.borderTiles()).filter((t) =>
this.mg.isOceanShore(t),
@@ -639,23 +571,6 @@ export class FakeHumanExecution implements Execution {
return null;
}
sendAttack(toAttack: Player | TerraNullius) {
if (toAttack.isPlayer() && this.player.isOnSameTeam(toAttack)) return;
const max =
this.mg.config().maxPopulation(this.player) *
this.player.targetTroopRatio();
const target = max * this.reserveRatio;
const troops = this.player.troops() - target;
if (troops < 1) return;
this.mg.addExecution(
new AttackExecution(
troops,
this.player.id(),
toAttack.isPlayer() ? toAttack.id() : null,
),
);
}
private randOceanShoreTile(tile: TileRef, dist: number): TileRef | null {
const x = this.mg.x(tile);
const y = this.mg.y(tile);
-1
View File
@@ -170,7 +170,6 @@ export class MirvExecution implements Execution {
if (!this.mg.isValidCoord(x, y)) {
continue;
}
console.log(`got coord ${x}, ${y}`);
const tile = this.mg.ref(x, y);
if (!this.mg.isLand(tile)) {
continue;
+100 -42
View File
@@ -19,8 +19,13 @@ export class SAMLauncherExecution implements Execution {
private active: boolean = true;
private target: Unit = null;
private warheadTargets: Unit[] = [];
private searchRangeRadius = 75;
private searchRangeRadius = 80;
// As MIRV go very fast we have to detect them very early but we only
// shoot the one targeting very close (MIRVWarheadProtectionRadius)
private MIRVWarheadSearchRadius = 400;
private MIRVWarheadProtectionRadius = 50;
private pseudoRandom: PseudoRandom;
@@ -39,6 +44,52 @@ export class SAMLauncherExecution implements Execution {
this.player = mg.player(this.ownerId);
}
private getSingleTarget(): Unit | null {
const nukes = this.mg
.nearbyUnits(this.sam.tile(), this.searchRangeRadius, [
UnitType.AtomBomb,
UnitType.HydrogenBomb,
])
.filter(
({ unit }) =>
unit.owner() !== this.player && !this.player.isFriendly(unit.owner()),
);
return (
nukes.sort((a, b) => {
const { unit: unitA, distSquared: distA } = a;
const { unit: unitB, distSquared: distB } = b;
// Prioritize Hydrogen Bombs
if (
unitA.type() === UnitType.HydrogenBomb &&
unitB.type() !== UnitType.HydrogenBomb
)
return -1;
if (
unitA.type() !== UnitType.HydrogenBomb &&
unitB.type() === UnitType.HydrogenBomb
)
return 1;
// If both are the same type, sort by distance (lower `distSquared` means closer)
return distA - distB;
})[0]?.unit ?? null
);
}
private isHit(type: UnitType, random: number): boolean {
if (type == UnitType.AtomBomb) {
return true;
}
if (type == UnitType.MIRVWarhead) {
return random < this.mg.config().samWarheadHittingChance();
}
return random < this.mg.config().samHittingChance();
}
tick(ticks: number): void {
if (this.sam == null) {
const spawnTile = this.player.canBuild(UnitType.SAMLauncher, this.tile);
@@ -64,36 +115,26 @@ export class SAMLauncherExecution implements Execution {
this.pseudoRandom = new PseudoRandom(this.sam.id());
}
const nukes = this.mg
.nearbyUnits(this.sam.tile(), this.searchRangeRadius, [
UnitType.AtomBomb,
UnitType.HydrogenBomb,
])
this.warheadTargets = this.mg
.nearbyUnits(
this.sam.tile(),
this.MIRVWarheadSearchRadius,
UnitType.MIRVWarhead,
)
.map(({ unit }) => unit)
.filter(
({ unit }) =>
(unit) =>
unit.owner() !== this.player && !this.player.isFriendly(unit.owner()),
)
.filter(
(unit) =>
this.mg.manhattanDist(unit.detonationDst(), this.sam.tile()) <
this.MIRVWarheadProtectionRadius,
);
this.target =
nukes.sort((a, b) => {
const { unit: unitA, distSquared: distA } = a;
const { unit: unitB, distSquared: distB } = b;
// Prioritize Hydrogen Bombs
if (
unitA.type() === UnitType.HydrogenBomb &&
unitB.type() !== UnitType.HydrogenBomb
)
return -1;
if (
unitA.type() !== UnitType.HydrogenBomb &&
unitB.type() === UnitType.HydrogenBomb
)
return 1;
// If both are the same type, sort by distance (lower `distSquared` means closer)
return distA - distB;
})[0]?.unit ?? null;
if (this.warheadTargets.length == 0) {
this.target = this.getSingleTarget();
}
if (
this.sam.isCooldown() &&
@@ -102,29 +143,46 @@ export class SAMLauncherExecution implements Execution {
this.sam.setCooldown(false);
}
if (this.target && !this.sam.isCooldown() && !this.target.targetedBySAM()) {
const isSingleTarget = this.target && !this.target.targetedBySAM();
if (
(isSingleTarget || this.warheadTargets.length > 0) &&
!this.sam.isCooldown()
) {
this.sam.setCooldown(true);
const type =
this.warheadTargets.length > 0
? UnitType.MIRVWarhead
: this.target.type();
const random = this.pseudoRandom.next();
let hit = true;
if (this.target.type() != UnitType.AtomBomb) {
hit = random < this.mg.config().samHittingChance();
}
const hit = this.isHit(type, random);
if (!hit) {
this.mg.displayMessage(
`Missile failed to intercept ${this.target.type()}`,
`Missile failed to intercept ${type}`,
MessageType.ERROR,
this.sam.owner().id(),
);
} else {
this.target.setTargetedBySAM(true);
this.mg.addExecution(
new SAMMissileExecution(
this.sam.tile(),
this.sam.owner(),
this.sam,
this.target,
),
);
if (this.warheadTargets.length > 0) {
// Message
this.mg.displayMessage(
`${this.warheadTargets.length} MIRV warheads intercepted`,
MessageType.SUCCESS,
this.sam.owner().id(),
);
// Delete warheads
this.warheadTargets.forEach((u) => u.delete());
} else {
this.target.setTargetedBySAM(true);
this.mg.addExecution(
new SAMMissileExecution(
this.sam.tile(),
this.sam.owner(),
this.sam,
this.target,
),
);
this.warheadTargets = [];
}
}
}
}
+191
View File
@@ -0,0 +1,191 @@
import {
AllianceRequest,
Game,
Player,
PlayerType,
Relation,
TerraNullius,
Tick,
} from "../../game/Game";
import { PseudoRandom } from "../../PseudoRandom";
import { AttackExecution } from "../AttackExecution";
import { EmojiExecution } from "../EmojiExecution";
export class BotBehavior {
private enemy: Player | null = null;
private enemyUpdated: Tick;
constructor(
private random: PseudoRandom,
private game: Game,
private player: Player,
private triggerRatio: number,
private reserveRatio: number,
) {}
handleAllianceRequests() {
for (const req of this.player.incomingAllianceRequests()) {
if (shouldAcceptAllianceRequest(this.player, req)) {
req.accept();
} else {
req.reject();
}
}
}
private emoji(player: Player, emoji: string) {
if (player.type() !== PlayerType.Human) return;
this.game.addExecution(
new EmojiExecution(this.player.id(), player.id(), emoji),
);
}
forgetOldEnemies() {
// Forget old enemies
if (this.game.ticks() - this.enemyUpdated > 100) {
this.enemy = null;
}
}
checkIncomingAttacks() {
// Switch enemies if we're under attack
const incomingAttacks = this.player.incomingAttacks();
if (incomingAttacks.length > 0) {
this.enemy = incomingAttacks
.sort((a, b) => b.troops() - a.troops())[0]
.attacker();
this.enemyUpdated = this.game.ticks();
}
}
assistAllies() {
outer: for (const ally of this.player.allies()) {
if (ally.targets().length === 0) continue;
if (this.player.relation(ally) < Relation.Friendly) {
// this.emoji(ally, "🤦");
continue;
}
for (const target of ally.targets()) {
if (target === this.player) {
// this.emoji(ally, "💀");
continue;
}
if (this.player.isAlliedWith(target)) {
// this.emoji(ally, "👎");
continue;
}
// All checks passed, assist them
this.player.updateRelation(ally, -20);
this.enemy = target;
this.enemyUpdated = this.game.ticks();
this.emoji(ally, "👍");
break outer;
}
}
}
selectEnemy(): Player | null {
if (this.enemy === null) {
// Save up troops until we reach the trigger ratio
const maxPop = this.game.config().maxPopulation(this.player);
const ratio = this.player.population() / maxPop;
if (ratio < this.triggerRatio) return null;
}
// Prefer neighboring bots
if (this.enemy === null) {
const bots = this.player
.neighbors()
.filter((n) => n.isPlayer() && n.type() === PlayerType.Bot) as Player[];
if (bots.length > 0) {
const density = (p: Player) => p.troops() / p.numTilesOwned();
this.enemy = bots.sort((a, b) => density(a) - density(b))[0];
this.enemyUpdated = this.game.ticks();
}
}
// Select the most hated player
if (this.enemy === null) {
const mostHated = this.player.allRelationsSorted()[0] ?? null;
if (mostHated != null && mostHated.relation === Relation.Hostile) {
this.enemy = mostHated.player;
this.enemyUpdated = this.game.ticks();
}
}
// Sanity check, don't attack our allies or teammates
if (this.enemy && this.player.isFriendly(this.enemy)) {
this.enemy = null;
}
return this.enemy;
}
selectRandomEnemy(): Player | TerraNullius | null {
if (this.enemy === null) {
// Save up troops until we reach the trigger ratio
const maxPop = this.game.config().maxPopulation(this.player);
const ratio = this.player.population() / maxPop;
if (ratio < this.triggerRatio) return null;
// Choose a new enemy randomly
const neighbors = this.player.neighbors();
for (const neighbor of this.random.shuffleArray(neighbors)) {
if (neighbor.isPlayer()) {
if (this.player.isFriendly(neighbor)) continue;
if (neighbor.type() == PlayerType.FakeHuman) {
if (this.random.chance(2)) {
continue;
}
}
}
this.enemy = neighbor;
this.enemyUpdated = this.game.ticks();
}
// Select a traitor as an enemy
const traitors = this.player
.neighbors()
.filter((n) => n.isPlayer() && n.isTraitor()) as Player[];
if (traitors.length > 0) {
const toAttack = this.random.randElement(traitors);
const odds = this.player.isFriendly(toAttack) ? 6 : 3;
if (this.random.chance(odds)) {
this.enemy = toAttack;
this.enemyUpdated = this.game.ticks();
}
}
}
// Sanity check, don't attack our allies or teammates
if (this.enemy && this.player.isFriendly(this.enemy)) {
this.enemy = null;
}
return this.enemy;
}
sendAttack(target: Player | TerraNullius) {
if (target.isPlayer() && this.player.isOnSameTeam(target)) return;
const maxPop = this.game.config().maxPopulation(this.player);
const maxTroops = maxPop * this.player.targetTroopRatio();
const targetTroops = maxTroops * this.reserveRatio;
const troops = this.player.troops() - targetTroops;
if (troops < 1) return;
this.game.addExecution(
new AttackExecution(
troops,
this.player.id(),
target.isPlayer() ? target.id() : null,
),
);
}
}
function shouldAcceptAllianceRequest(player: Player, request: AllianceRequest) {
const notTraitor = !request.requestor().isTraitor();
const noMalice = player.relation(request.requestor()) >= Relation.Neutral;
const requestorIsMuchLarger =
request.requestor().numTilesOwned() > player.numTilesOwned() * 3;
const notTooManyAlliances =
requestorIsMuchLarger || request.requestor().alliances().length < 3;
return notTraitor && noMalice && notTooManyAlliances;
}
+4 -2
View File
@@ -67,6 +67,7 @@ export enum GameMapType {
Japan = "Japan",
BetweenTwoSeas = "Between Two Seas",
KnownWorld = "Known World",
FaroeIslands = "FaroeIslands",
}
export enum GameType {
@@ -314,6 +315,7 @@ export interface Player {
// State & Properties
isAlive(): boolean;
isTraitor(): boolean;
markTraitor(): void;
largestClusterBoundingBox: { min: Cell; max: Cell } | null;
lastTileChange(): Tick;
@@ -414,7 +416,7 @@ export interface Player {
// Misc
toUpdate(): PlayerUpdate;
playerProfile(): PlayerProfile;
canBoat(tile: TileRef): boolean;
canBoat(tile: TileRef): TileRef | false;
tradingPorts(port: Unit): Unit[];
}
@@ -472,7 +474,7 @@ export interface Game extends GameMap {
}
export interface PlayerActions {
canBoat: boolean;
canBoat: TileRef | false;
canAttack: boolean;
buildableUnits: BuildableUnit[];
canSendEmojiAllPlayers: boolean;
+1 -1
View File
@@ -518,7 +518,7 @@ export class GameImpl implements Game {
);
}
if (!other.isTraitor()) {
(breaker as PlayerImpl).isTraitor_ = true;
breaker.markTraitor();
}
const breakerSet = new Set(breaker.alliances());
+23 -8
View File
@@ -22,6 +22,7 @@ import {
Attack,
Cell,
EmojiMessage,
GameMode,
Gold,
MessageType,
MutableAlliance,
@@ -67,7 +68,7 @@ export class PlayerImpl implements Player {
// 0 to 100
private _targetTroopRatio: bigint;
isTraitor_ = false;
markedTraitorTick = -1;
private embargoes: Set<PlayerID> = new Set();
@@ -243,7 +244,7 @@ export class PlayerImpl implements Player {
const ns: Set<Player | TerraNullius> = new Set();
for (const border of this.borderTiles()) {
for (const neighbor of this.mg.map().neighbors(border)) {
if (this.mg.map().isLake(neighbor)) {
if (this.mg.map().isLand(neighbor)) {
const owner = this.mg.map().ownerID(neighbor);
if (owner != this.smallID()) {
ns.add(
@@ -372,7 +373,14 @@ export class PlayerImpl implements Player {
}
isTraitor(): boolean {
return this.isTraitor_;
return (
this.markedTraitorTick >= 0 &&
this.mg.ticks() - this.markedTraitorTick <
this.mg.config().traitorDuration()
);
}
markTraitor(): void {
this.markedTraitorTick = this.mg.ticks();
}
createAllianceRequest(recipient: Player): AllianceRequest {
@@ -516,6 +524,13 @@ export class PlayerImpl implements Player {
}
canDonate(recipient: Player): boolean {
if (
recipient.type() == PlayerType.Human &&
this.mg.config().gameConfig().gameMode == GameMode.FFA
) {
return false;
}
if (!this.isFriendly(recipient)) {
return false;
}
@@ -541,7 +556,7 @@ export class PlayerImpl implements Player {
this.id(),
);
this.mg.displayMessage(
`Recieved ${renderTroops(troops)} troops from ${this.name()}`,
`Received ${renderTroops(troops)} troops from ${this.name()}`,
MessageType.SUCCESS,
recipient.id(),
);
@@ -555,7 +570,7 @@ export class PlayerImpl implements Player {
this.id(),
);
this.mg.displayMessage(
`Recieved ${renderNumber(gold)} gold from ${this.name()}`,
`Received ${renderNumber(gold)} gold from ${this.name()}`,
MessageType.SUCCESS,
recipient.id(),
);
@@ -877,7 +892,7 @@ export class PlayerImpl implements Player {
return rel;
}
public canBoat(tile: TileRef): boolean {
public canBoat(tile: TileRef): TileRef | false {
if (
this.units(UnitType.TransportShip).length >=
this.mg.config().boatMaxNumber()
@@ -920,7 +935,7 @@ export class PlayerImpl implements Player {
}
if (myPlayerBordersOcean && otherPlayerBordersOcean) {
return this.canBuild(UnitType.TransportShip, dst) != false;
return this.canBuild(UnitType.TransportShip, dst);
} else {
return false;
}
@@ -943,7 +958,7 @@ export class PlayerImpl implements Player {
for (const t of sorted) {
if (this.mg.owner(t) == this) {
return this.canBuild(UnitType.TransportShip, dst) != false;
return this.canBuild(UnitType.TransportShip, dst);
}
}
return false;
+1
View File
@@ -41,6 +41,7 @@ const MAP_FILE_NAMES: Record<GameMapType, string> = {
[GameMapType.Japan]: "Japan",
[GameMapType.BetweenTwoSeas]: "BetweenTwoSeas",
[GameMapType.KnownWorld]: "KnownWorld",
[GameMapType.FaroeIslands]: "FaroeIslands",
};
class GameMapLoader {
+1 -1
View File
@@ -141,7 +141,7 @@ export class UnitImpl implements Unit {
this._active = false;
this.mg.addUpdate(this.toUpdate());
this.mg.removeUnit(this);
if (displayMessage) {
if (displayMessage && this.type() != UnitType.MIRVWarhead) {
this.mg.displayMessage(
`Your ${this.type()} was destroyed`,
MessageType.ERROR,
+1
View File
@@ -22,6 +22,7 @@ const maps = [
"BetweenTwoSeas",
"Japan",
"KnownWorld",
"FaroeIslands",
];
const removeSmall = true;
+3 -2
View File
@@ -86,8 +86,8 @@ export class MapPlaylist {
Africa: 2,
Britannia: 1,
GatewayToTheAtlantic: 2,
Australia: 1,
Iceland: 1,
Australia: 2,
Iceland: 2,
SouthAmerica: 3,
KnownWorld: 2,
};
@@ -101,6 +101,7 @@ export class MapPlaylist {
BetweenTwoSeas: 3,
Japan: 3,
BlackSea: 1,
FaroeIslands: 2,
};
}
}
+5 -3
View File
@@ -26,11 +26,13 @@ echo "Container name: ${CONTAINER_NAME}"
echo "Docker image: ${FULL_IMAGE_NAME}"
# Load environment variables if .env exists
if [ -f /root/.env ]; then
if [ -f /home/openfront/.env ]; then
echo "Loading environment variables from .env file..."
export $(grep -v '^#' /root/.env | xargs)
export $(grep -v '^#' /home/openfront/.env | xargs)
fi
docker login -u $DOCKER_USERNAME -p $DOCKER_TOKEN
# Install Loki Docker plugin if not already installed
if ! docker plugin ls | grep -q "loki"; then
echo "Installing Loki Docker plugin..."
@@ -99,7 +101,7 @@ docker run -d -p 80:80 -p 127.0.0.1:9090:9090 \
--log-opt loki-external-labels="job=docker,environment=${ENV},host=${REGION},region=${REGION}" \
--env GAME_ENV=${ENV} \
--env REGION=${REGION} \
--env-file /root/.env \
--env-file /home/openfront/.env \
--name ${CONTAINER_NAME} \
$FULL_IMAGE_NAME