diff --git a/Assets/Resources/Geonames.meta b/Assets/Resources/Geonames.meta deleted file mode 100644 index 64c8c9a19..000000000 --- a/Assets/Resources/Geonames.meta +++ /dev/null @@ -1,8 +0,0 @@ -fileFormatVersion: 2 -guid: 0f3e518a4c9bcc2469abe6cebdcb3691 -folderAsset: yes -DefaultImporter: - externalObjects: {} - userData: - assetBundleName: - assetBundleVariant: diff --git a/Assets/Resources/Geonames/Australia.csv b/Assets/Resources/Geonames/Australia.csv deleted file mode 100644 index 05587efaa..000000000 --- a/Assets/Resources/Geonames/Australia.csv +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:01bb478f4433dc42d5533e5030ef103ad163deca6bc40fb412025acd12e35c73 -size 442795 diff --git a/Assets/Resources/Geonames/Australia.csv.meta b/Assets/Resources/Geonames/Australia.csv.meta deleted file mode 100644 index 33d8c7dbe..000000000 --- a/Assets/Resources/Geonames/Australia.csv.meta +++ /dev/null @@ -1,7 +0,0 @@ -fileFormatVersion: 2 -guid: 8ba25d5d0ecee31408c7ce7460b4e015 -TextScriptImporter: - externalObjects: {} - userData: - assetBundleName: - assetBundleVariant: diff --git a/Assets/Resources/Geonames/Congo.csv b/Assets/Resources/Geonames/Congo.csv deleted file mode 100644 index 5d0f0b66e..000000000 --- a/Assets/Resources/Geonames/Congo.csv +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:c84ff44083e4f0d9d9314e7b7095ed4ce2955905f26e7926faa09ed222655729 -size 506722 diff --git a/Assets/Resources/Geonames/Congo.csv.meta b/Assets/Resources/Geonames/Congo.csv.meta deleted file mode 100644 index 6147d0a96..000000000 --- a/Assets/Resources/Geonames/Congo.csv.meta +++ /dev/null @@ -1,7 +0,0 @@ -fileFormatVersion: 2 -guid: 1e43b383bf860524986a6947496776d0 -TextScriptImporter: - externalObjects: {} - userData: - assetBundleName: - assetBundleVariant: diff --git a/Assets/Resources/Geonames/Germany.csv b/Assets/Resources/Geonames/Germany.csv deleted file mode 100644 index beb35d1d0..000000000 --- a/Assets/Resources/Geonames/Germany.csv +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:73e4e7231d448879d0a6d8524b3200be75fc8f5011a70cfd670b0897d07eca10 -size 1284208 diff --git a/Assets/Resources/Geonames/Germany.csv.meta b/Assets/Resources/Geonames/Germany.csv.meta deleted file mode 100644 index 244e41eda..000000000 --- a/Assets/Resources/Geonames/Germany.csv.meta +++ /dev/null @@ -1,7 +0,0 @@ -fileFormatVersion: 2 -guid: ed470b8eb6cc68c4485ddc05d5a17ca1 -TextScriptImporter: - externalObjects: {} - userData: - assetBundleName: - assetBundleVariant: diff --git a/Assets/Resources/Geonames/India.csv b/Assets/Resources/Geonames/India.csv deleted file mode 100644 index 6b4eab5c6..000000000 --- a/Assets/Resources/Geonames/India.csv +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:96c66414822c43a5451ce1910c1e545dedb224b0f3e79bc54f945dd484afc6a2 -size 3229324 diff --git a/Assets/Resources/Geonames/India.csv.meta b/Assets/Resources/Geonames/India.csv.meta deleted file mode 100644 index 3f8c176b4..000000000 --- a/Assets/Resources/Geonames/India.csv.meta +++ /dev/null @@ -1,7 +0,0 @@ -fileFormatVersion: 2 -guid: ca62f6d649360184aaa975c2716dfb27 -TextScriptImporter: - externalObjects: {} - userData: - assetBundleName: - assetBundleVariant: diff --git a/Assets/Resources/Geonames/Iran.csv b/Assets/Resources/Geonames/Iran.csv deleted file mode 100644 index 8d2754642..000000000 --- a/Assets/Resources/Geonames/Iran.csv +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:b70fbbe6b4832eb1a3f35844bd87c0eb479fc77b6e59d6600b64cbbe7cc1bf16 -size 1073119 diff --git a/Assets/Resources/Geonames/Iran.csv.meta b/Assets/Resources/Geonames/Iran.csv.meta deleted file mode 100644 index 507664280..000000000 --- a/Assets/Resources/Geonames/Iran.csv.meta +++ /dev/null @@ -1,7 +0,0 @@ -fileFormatVersion: 2 -guid: e2d60c7158f6a01418d78e46c5845729 -TextScriptImporter: - externalObjects: {} - userData: - assetBundleName: - assetBundleVariant: diff --git a/Assets/Resources/Geonames/Japan.csv b/Assets/Resources/Geonames/Japan.csv deleted file mode 100644 index d106ec364..000000000 --- a/Assets/Resources/Geonames/Japan.csv +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:d58dc0227cc6e3b7622726c155031467b6d7314623c7e3a33aad8ce1462b2c5b -size 1031132 diff --git a/Assets/Resources/Geonames/Japan.csv.meta b/Assets/Resources/Geonames/Japan.csv.meta deleted file mode 100644 index f46c1022d..000000000 --- a/Assets/Resources/Geonames/Japan.csv.meta +++ /dev/null @@ -1,7 +0,0 @@ -fileFormatVersion: 2 -guid: 5dd450a6f5ec92d49871674b33509135 -TextScriptImporter: - externalObjects: {} - userData: - assetBundleName: - assetBundleVariant: diff --git a/Assets/Resources/Geonames/Mexico.csv b/Assets/Resources/Geonames/Mexico.csv deleted file mode 100644 index 517228e8f..000000000 --- a/Assets/Resources/Geonames/Mexico.csv +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:9a654b8451a08a0c769712f4fb1d96c3c661457a88bb57ec4288eab36eab7ec1 -size 633432 diff --git a/Assets/Resources/Geonames/Mexico.csv.meta b/Assets/Resources/Geonames/Mexico.csv.meta deleted file mode 100644 index 2b0283e78..000000000 --- a/Assets/Resources/Geonames/Mexico.csv.meta +++ /dev/null @@ -1,7 +0,0 @@ -fileFormatVersion: 2 -guid: b75e17ea31f6a62488628bf9605bd48c -TextScriptImporter: - externalObjects: {} - userData: - assetBundleName: - assetBundleVariant: diff --git a/Assets/Resources/Geonames/Russia.csv b/Assets/Resources/Geonames/Russia.csv deleted file mode 100644 index 6344ff571..000000000 --- a/Assets/Resources/Geonames/Russia.csv +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:f4ed581eef681e5357a14efe4e6a1fc52370def7ada6565f4b953e410fa7f39e -size 2375319 diff --git a/Assets/Resources/Geonames/Russia.csv.meta b/Assets/Resources/Geonames/Russia.csv.meta deleted file mode 100644 index 7794294e9..000000000 --- a/Assets/Resources/Geonames/Russia.csv.meta +++ /dev/null @@ -1,7 +0,0 @@ -fileFormatVersion: 2 -guid: 5c1aee30ccf3a1e44bddeb4d550edbf0 -TextScriptImporter: - externalObjects: {} - userData: - assetBundleName: - assetBundleVariant: diff --git a/Assets/Resources/Geonames/Swahili.csv b/Assets/Resources/Geonames/Swahili.csv deleted file mode 100644 index 678065eed..000000000 --- a/Assets/Resources/Geonames/Swahili.csv +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:fb0f44e7948c637a2b0f9e0ca098b2f4689d065fe6484723acf0bfa34a26d0a5 -size 501002 diff --git a/Assets/Resources/Geonames/Swahili.csv.meta b/Assets/Resources/Geonames/Swahili.csv.meta deleted file mode 100644 index 94bfbb4f8..000000000 --- a/Assets/Resources/Geonames/Swahili.csv.meta +++ /dev/null @@ -1,7 +0,0 @@ -fileFormatVersion: 2 -guid: 63e21276398c8b0449e01d97a088add1 -TextScriptImporter: - externalObjects: {} - userData: - assetBundleName: - assetBundleVariant: diff --git a/Assets/Resources/Geonames/Thailand.csv b/Assets/Resources/Geonames/Thailand.csv deleted file mode 100644 index 400b8f1a9..000000000 --- a/Assets/Resources/Geonames/Thailand.csv +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:888c929f0e29728369448e6981bb8a043c49610f4c4390a2cb549bf6b494d3dc -size 143084 diff --git a/Assets/Resources/Geonames/Thailand.csv.meta b/Assets/Resources/Geonames/Thailand.csv.meta deleted file mode 100644 index fd5bd346a..000000000 --- a/Assets/Resources/Geonames/Thailand.csv.meta +++ /dev/null @@ -1,7 +0,0 @@ -fileFormatVersion: 2 -guid: 2923f8bd3a2f0fb4084f64138e6d793d -TextScriptImporter: - externalObjects: {} - userData: - assetBundleName: - assetBundleVariant: diff --git a/Assets/Resources/Geonames/USA.csv b/Assets/Resources/Geonames/USA.csv deleted file mode 100644 index 05bd91140..000000000 --- a/Assets/Resources/Geonames/USA.csv +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:314a30df2033795f04738589c660991e4310f1bfe0578c71bda2a9a72d8a617f -size 954928 diff --git a/Assets/Resources/Geonames/USA.csv.meta b/Assets/Resources/Geonames/USA.csv.meta deleted file mode 100644 index b59cdf567..000000000 --- a/Assets/Resources/Geonames/USA.csv.meta +++ /dev/null @@ -1,7 +0,0 @@ -fileFormatVersion: 2 -guid: a06c0e26f73d51f489303deadad77030 -TextScriptImporter: - externalObjects: {} - userData: - assetBundleName: - assetBundleVariant: diff --git a/Assets/Resources/Geonames/bad-words.txt b/Assets/Resources/Geonames/bad-words.txt deleted file mode 100644 index 5013fea9d..000000000 --- a/Assets/Resources/Geonames/bad-words.txt +++ /dev/null @@ -1,1384 +0,0 @@ - -abbo -abo -abortion -abuse -addict -addicts -adult -africa -african -alla -allah -alligatorbait -amateur -american -anal -analannie -analsex -angie -angry -anus -arab -arabs -areola -argie -aroused -arse -arsehole -asian -ass -assassin -assassinate -assassination -assault -assbagger -assblaster -assclown -asscowboy -asses -assfuck -assfucker -asshat -asshole -assholes -asshore -assjockey -asskiss -asskisser -assklown -asslick -asslicker -asslover -assman -assmonkey -assmunch -assmuncher -asspacker -asspirate -asspuppies -assranger -asswhore -asswipe -athletesfoot -attack -australian -babe -babies -backdoor -backdoorman -backseat -badfuck -balllicker -balls -ballsack -banging -baptist -barelylegal -barf -barface -barfface -bast -bastard -bazongas -bazooms -beaner -beast -beastality -beastial -beastiality -beatoff -beat-off -beatyourmeat -beaver -bestial -bestiality -bi -biatch -bible -bicurious -bigass -bigbastard -bigbutt -bigger -bisexual -bi-sexual -bitch -bitcher -bitches -bitchez -bitchin -bitching -bitchslap -bitchy -biteme -black -blackman -blackout -blacks -blind -blow -blowjob -boang -bogan -bohunk -bollick -bollock -bomb -bombers -bombing -bombs -bomd -bondage -boner -bong -boob -boobies -boobs -booby -boody -boom -boong -boonga -boonie -booty -bootycall -bountybar -bra -brea5t -breast -breastjob -breastlover -breastman -brothel -bugger -buggered -buggery -bullcrap -bulldike -bulldyke -bullshit -bumblefuck -bumfuck -bunga -bunghole -buried -burn -butchbabes -butchdike -butchdyke -butt -buttbang -butt-bang -buttface -buttfuck -butt-fuck -buttfucker -butt-fucker -buttfuckers -butt-fuckers -butthead -buttman -buttmunch -buttmuncher -buttpirate -buttplug -buttstain -byatch -cacker -cameljockey -cameltoe -canadian -cancer -carpetmuncher -carruth -catholic -catholics -cemetery -chav -cherrypopper -chickslick -children's -chin -chinaman -chinamen -chinese -chink -chinky -choad -chode -christ -christian -church -cigarette -cigs -clamdigger -clamdiver -clit -clitoris -clogwog -cocaine -cock -cockblock -cockblocker -cockcowboy -cockfight -cockhead -cockknob -cocklicker -cocklover -cocknob -cockqueen -cockrider -cocksman -cocksmith -cocksmoker -cocksucer -cocksuck -cocksucked -cocksucker -cocksucking -cocktail -cocktease -cocky -cohee -coitus -color -colored -coloured -commie -communist -condom -conservative -conspiracy -coolie -cooly -coon -coondog -copulate -cornhole -corruption -cra5h -crabs -crack -crackpipe -crackwhore -crack-whore -crap -crapola -crapper -crappy -crash -creamy -crime -crimes -criminal -criminals -crotch -crotchjockey -crotchmonkey -crotchrot -cum -cumbubble -cumfest -cumjockey -cumm -cummer -cumming -cumquat -cumqueen -cumshot -cunilingus -cunillingus -cunn -cunnilingus -cunntt -cunt -cunteyed -cuntfuck -cuntfucker -cuntlick -cuntlicker -cuntlicking -cuntsucker -cybersex -cyberslimer -dago -dahmer -dammit -damn -damnation -damnit -darkie -darky -datnigga -dead -deapthroat -death -deepthroat -defecate -dego -demon -deposit -desire -destroy -deth -devil -devilworshipper -dick -dickbrain -dickforbrains -dickhead -dickless -dicklick -dicklicker -dickman -dickwad -dickweed -diddle -die -died -dies -dike -dildo -dingleberry -dink -dipshit -dipstick -dirty -disease -diseases -disturbed -dive -dix -dixiedike -dixiedyke -doggiestyle -doggystyle -dong -doodoo -doo-doo -doom -dope -dragqueen -dragqween -dripdick -drug -drunk -drunken -dumb -dumbass -dumbbitch -dumbfuck -dyefly -dyke -easyslut -eatballs -eatme -eatpussy -ecstacy -ejaculate -ejaculated -ejaculating -ejaculation -enema -enemy -erect -erection -ero -escort -ethiopian -ethnic -european -evl -excrement -execute -executed -execution -executioner -explosion -facefucker -faeces -fag -fagging -faggot -fagot -failed -failure -fairies -fairy -faith -fannyfucker -fart -farted -farting -farty -fastfuck -fat -fatah -fatass -fatfuck -fatfucker -fatso -fckcum -fear -feces -felatio -felch -felcher -felching -fellatio -feltch -feltcher -feltching -fetish -fight -filipina -filipino -fingerfood -fingerfuck -fingerfucked -fingerfucker -fingerfuckers -fingerfucking -fire -firing -fister -fistfuck -fistfucked -fistfucker -fistfucking -fisting -flange -flasher -flatulence -floo -flydie -flydye -fok -fondle -footaction -footfuck -footfucker -footlicker -footstar -fore -foreskin -forni -fornicate -foursome -fourtwenty -fraud -freakfuck -freakyfucker -freefuck -fu -fubar -fuc -fucck -fuck -fucka -fuckable -fuckbag -fuckbuddy -fucked -fuckedup -fucker -fuckers -fuckface -fuckfest -fuckfreak -fuckfriend -fuckhead -fuckher -fuckin -fuckina -fucking -fuckingbitch -fuckinnuts -fuckinright -fuckit -fuckknob -fuckme -fuckmehard -fuckmonkey -fuckoff -fuckpig -fucks -fucktard -fuckwhore -fuckyou -fudgepacker -fugly -fuk -fuks -funeral -funfuck -fungus -fuuck -gangbang -gangbanged -gangbanger -gangsta -gatorbait -gay -gaymuthafuckinwhore -gaysex -geez -geezer -geni -genital -german -getiton -gin -ginzo -gipp -girls -givehead -glazeddonut -gob -god -godammit -goddamit -goddammit -goddamn -goddamned -goddamnes -goddamnit -goddamnmuthafucker -goldenshower -gonorrehea -gonzagas -gook -gotohell -goy -goyim -greaseball -gringo -groe -gross -grostulation -gubba -gummer -gun -gyp -gypo -gypp -gyppie -gyppo -gyppy -hamas -handjob -hapa -harder -hardon -harem -headfuck -headlights -hebe -heeb -hell -henhouse -heroin -herpes -heterosexual -hijack -hijacker -hijacking -hillbillies -hindoo -hiscock -hitler -hitlerism -hitlerist -hiv -ho -hobo -hodgie -hoes -hole -holestuffer -homicide -homo -homobangers -homosexual -honger -honk -honkers -honkey -honky -hook -hooker -hookers -hooters -hore -hork -horn -horney -horniest -horny -horseshit -hosejob -hoser -hostage -hotdamn -hotpussy -hottotrot -hummer -husky -hussy -hustler -hymen -hymie -iblowu -idiot -ikey -illegal -incest -insest -intercourse -interracial -intheass -inthebuff -israel -israeli -israel's -italiano -itch -jackass -jackoff -jackshit -jacktheripper -jade -jap -japanese -japcrap -jebus -jeez -jerkoff -jesus -jesuschrist -jew -jewish -jiga -jigaboo -jigg -jigga -jiggabo -jigger -jiggy -jihad -jijjiboo -jimfish -jism -jiz -jizim -jizjuice -jizm -jizz -jizzim -jizzum -joint -juggalo -jugs -junglebunny -kaffer -kaffir -kaffre -kafir -kanake -kid -kigger -kike -kill -killed -killer -killing -kills -kink -kinky -kissass -kkk -knife -knockers -kock -kondum -koon -kotex -krap -krappy -kraut -kum -kumbubble -kumbullbe -kummer -kumming -kumquat -kums -kunilingus -kunnilingus -kunt -ky -kyke -lactate -laid -lapdance -latin -lesbain -lesbayn -lesbian -lesbin -lesbo -lez -lezbe -lezbefriends -lezbo -lezz -lezzo -liberal -libido -licker -lickme -lies -limey -limpdick -limy -lingerie -liquor -livesex -loadedgun -lolita -looser -loser -lotion -lovebone -lovegoo -lovegun -lovejuice -lovemuscle -lovepistol -loverocket -lowlife -lsd -lubejob -lucifer -luckycammeltoe -lugan -lynch -macaca -mad -mafia -magicwand -mams -manhater -manpaste -marijuana -mastabate -mastabater -masterbate -masterblaster -mastrabator -masturbate -masturbating -mattressprincess -meatbeatter -meatrack -meth -mexican -mgger -mggor -mickeyfinn -mideast -milf -minority -mockey -mockie -mocky -mofo -moky -moles -molest -molestation -molester -molestor -moneyshot -mooncricket -mormon -moron -moslem -mosshead -mothafuck -mothafucka -mothafuckaz -mothafucked -mothafucker -mothafuckin -mothafucking -mothafuckings -motherfuck -motherfucked -motherfucker -motherfuckin -motherfucking -motherfuckings -motherlovebone -muff -muffdive -muffdiver -muffindiver -mufflikcer -mulatto -muncher -munt -murder -murderer -muslim -naked -narcotic -nasty -nastybitch -nastyho -nastyslut -nastywhore -nazi -necro -negro -negroes -negroid -negro's -nig -niger -nigerian -nigerians -nigg -nigga -niggah -niggaracci -niggard -niggarded -niggarding -niggardliness -niggardliness's -niggardly -niggards -niggard's -niggaz -nigger -niggerhead -niggerhole -niggers -nigger's -niggle -niggled -niggles -niggling -nigglings -niggor -niggur -niglet -nignog -nigr -nigra -nigre -nip -nipple -nipplering -nittit -nlgger -nlggor -nofuckingway -nook -nookey -nookie -noonan -nooner -nude -nudger -nuke -nutfucker -nymph -ontherag -oral -orga -orgasim -orgasm -orgies -orgy -osama -paki -palesimian -palestinian -pansies -pansy -panti -panties -payo -pearlnecklace -peck -pecker -peckerwood -pee -peehole -pee-pee -peepshow -peepshpw -pendy -penetration -peni5 -penile -penis -penises -penthouse -period -perv -phonesex -phuk -phuked -phuking -phukked -phukking -phungky -phuq -pi55 -picaninny -piccaninny -pickaninny -piker -pikey -piky -pimp -pimped -pimper -pimpjuic -pimpjuice -pimpsimp -pindick -piss -pissed -pisser -pisses -pisshead -pissin -pissing -pissoff -pistol -pixie -pixy -playboy -playgirl -pocha -pocho -pocketpool -pohm -polack -pom -pommie -pommy -poo -poon -poontang -poop -pooper -pooperscooper -pooping -poorwhitetrash -popimp -porchmonkey -porn -pornflick -pornking -porno -pornography -pornprincess -pot -poverty -premature -pric -prick -prickhead -primetime -propaganda -pros -prostitute -protestant -pu55i -pu55y -pube -pubic -pubiclice -pud -pudboy -pudd -puddboy -puke -puntang -purinapricness -puss -pussie -pussies -pussy -pussycat -pussyeater -pussyfucker -pussylicker -pussylips -pussylover -pussypounder -pusy -quashie -queef -queer -quickie -quim -ra8s -rabbi -racial -racist -radical -radicals -raghead -randy -rape -raped -raper -rapist -rearend -rearentry -rectum -redlight -redneck -reefer -reestie -refugee -reject -remains -rentafuck -republican -rere -retard -retarded -ribbed -rigger -rimjob -rimming -roach -robber -roundeye -rump -russki -russkie -sadis -sadom -samckdaddy -sandm -sandnigger -satan -scag -scallywag -scat -schlong -screw -screwyou -scrotum -scum -semen -seppo -servant -sex -sexed -sexfarm -sexhound -sexhouse -sexing -sexkitten -sexpot -sexslave -sextogo -sextoy -sextoys -sexual -sexually -sexwhore -sexy -sexymoma -sexy-slim -shag -shaggin -shagging -shat -shav -shawtypimp -sheeney -shhit -shinola -shit -shitcan -shitdick -shite -shiteater -shited -shitface -shitfaced -shitfit -shitforbrains -shitfuck -shitfucker -shitfull -shithapens -shithappens -shithead -shithouse -shiting -shitlist -shitola -shitoutofluck -shits -shitstain -shitted -shitter -shitting -shitty -shoot -shooting -shortfuck -showtime -sick -sissy -sixsixsix -sixtynine -sixtyniner -skank -skankbitch -skankfuck -skankwhore -skanky -skankybitch -skankywhore -skinflute -skum -skumbag -slant -slanteye -slapper -slaughter -slav -slave -slavedriver -sleezebag -sleezeball -slideitin -slime -slimeball -slimebucket -slopehead -slopey -slopy -slut -sluts -slutt -slutting -slutty -slutwear -slutwhore -smack -smackthemonkey -smut -snatch -snatchpatch -snigger -sniggered -sniggering -sniggers -snigger's -sniper -snot -snowback -snownigger -sob -sodom -sodomise -sodomite -sodomize -sodomy -sonofabitch -sonofbitch -sooty -sos -soviet -spaghettibender -spaghettinigger -spank -spankthemonkey -sperm -spermacide -spermbag -spermhearder -spermherder -spic -spick -spig -spigotty -spik -spit -spitter -splittail -spooge -spreadeagle -spunk -spunky -squaw -stagg -stiffy -strapon -stringer -stripclub -stroke -stroking -stupid -stupidfuck -stupidfucker -suck -suckdick -sucker -suckme -suckmyass -suckmydick -suckmytit -suckoff -suicide -swallow -swallower -swalow -swastika -sweetness -syphilis -taboo -taff -tampon -tang -tantra -tarbaby -tard -teat -terror -terrorist -teste -testicle -testicles -thicklips -thirdeye -thirdleg -threesome -threeway -timbernigger -tinkle -tit -titbitnipply -titfuck -titfucker -titfuckin -titjob -titlicker -titlover -tits -tittie -titties -titty -tnt -toilet -tongethruster -tongue -tonguethrust -tonguetramp -tortur -torture -tosser -towelhead -trailertrash -tramp -trannie -tranny -transexual -transsexual -transvestite -triplex -trisexual -trojan -trots -tuckahoe -tunneloflove -turd -turnon -twat -twink -twinkie -twobitwhore -uck -uk -unfuckable -upskirt -uptheass -upthebutt -urinary -urinate -urine -usama -uterus -vagina -vaginal -vatican -vibr -vibrater -vibrator -vietcong -violence -virgin -virginbreaker -vomit -vulva -wab -wank -wanker -wanking -waysted -weapon -weenie -weewee -welcher -welfare -wetb -wetback -wetspot -whacker -whash -whigger -whiskey -whiskeydick -whiskydick -whit -whitenigger -whites -whitetrash -whitey -whiz -whop -whore -whorefucker -whorehouse -wigger -willie -williewanker -willy -wn -wog -women's -wop -wtf -wuss -wuzzie -xtc -xxx -yankee -yellowman -zigabo -zipperhead diff --git a/Assets/Resources/Geonames/bad-words.txt.meta b/Assets/Resources/Geonames/bad-words.txt.meta deleted file mode 100644 index ff605e0c5..000000000 --- a/Assets/Resources/Geonames/bad-words.txt.meta +++ /dev/null @@ -1,7 +0,0 @@ -fileFormatVersion: 2 -guid: 790ef350f8658b04480e0645919a21fa -TextScriptImporter: - externalObjects: {} - userData: - assetBundleName: - assetBundleVariant: diff --git a/Assets/Resources/Geonames/pleiades.csv b/Assets/Resources/Geonames/pleiades.csv deleted file mode 100644 index 581d44ff7..000000000 --- a/Assets/Resources/Geonames/pleiades.csv +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:45b0dbc07d492958b26de3695427076d270bdd0525ab45c415be72560ebccb5f -size 400944 diff --git a/Assets/Resources/Geonames/pleiades.csv.meta b/Assets/Resources/Geonames/pleiades.csv.meta deleted file mode 100644 index dc0edb8f1..000000000 --- a/Assets/Resources/Geonames/pleiades.csv.meta +++ /dev/null @@ -1,7 +0,0 @@ -fileFormatVersion: 2 -guid: b532694a5ec3cc6458d42aa2835537b5 -TextScriptImporter: - externalObjects: {} - userData: - assetBundleName: - assetBundleVariant: diff --git a/Assets/Resources/Settings.asset b/Assets/Resources/Settings.asset index a899ee58b..f4c21ce13 100644 --- a/Assets/Resources/Settings.asset +++ b/Assets/Resources/Settings.asset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:7d4f98ca0bfca9f5e43f00ee758cb7889b701e87b54eff2082b49439653464d6 -size 12439 +oid sha256:95d1ba64331895cc66862010f94a8cd4598ec9dd211a83b14b6091ac948afd56 +size 12327 diff --git a/Assets/Scripts/Editor/NameTools.cs b/Assets/Scripts/Editor/NameTools.cs deleted file mode 100644 index a048dce89..000000000 --- a/Assets/Scripts/Editor/NameTools.cs +++ /dev/null @@ -1,93 +0,0 @@ -using System.Buffers.Text; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Text.RegularExpressions; -using MessagePack; -using CultMath; -using UnityEngine; -using UnityEditor; -using Random = CultMath.Random; - -public class NameTools : EditorWindow -{ - public int NameGeneratorMinLength = 5; - public int NameGeneratorMaxLength = 10; - public int NameGeneratorOrder = 4; - private int minWordLength = 4; - private TextAsset nameFile; - private MarkovNameGenerator _nameGenerator; - private bool _stripNumberTokens; - - - // Add menu named "My Window" to the Window menu - [MenuItem("Window/Aetheria/Name Tools")] - static void Init() - { - // Get existing open window or if none, make a new one: - NameTools window = (NameTools)EditorWindow.GetWindow(typeof(NameTools)); - window.Show(); - } - - bool HasNonASCIIChars(string str) - { - return (System.Text.Encoding.UTF8.GetByteCount(str) != str.Length); - } - - void OnGUI() - { - - nameFile = (TextAsset) EditorGUILayout.ObjectField("Name File", nameFile, typeof(TextAsset), false); - minWordLength = EditorGUILayout.IntField("Minimum File Word Length", minWordLength); - NameGeneratorMinLength = EditorGUILayout.IntField("Generated Minimum Word Length", NameGeneratorMinLength); - NameGeneratorMaxLength = EditorGUILayout.IntField("Generated Maximum Word Length", NameGeneratorMaxLength); - NameGeneratorOrder = EditorGUILayout.IntField("Generator Order", NameGeneratorOrder); - _stripNumberTokens = GUILayout.Toggle(_stripNumberTokens, "Strip Number Tokens"); - - if (GUILayout.Button("Clean Name File")) - { - var lines = nameFile.text.Split('\n'); - using StreamWriter outputFile = new StreamWriter(Path.Combine(Application.dataPath, nameFile.name + ".csv")); - var names = new HashSet(); - foreach (var line in lines) - { - var tokens = line.Split(',', ' '); - foreach (var t in tokens) - { - if (!HasNonASCIIChars(t)) - { - var s = new string(t.Where(c => char.IsLetter(c) || c == '-' || c == '`' || c == '\'').ToArray()).Trim().Trim('`','-'); - if(s.Length >= minWordLength && !names.Contains(s)) - { - names.Add(s); - outputFile.WriteLine(s); - } - } - } - } - } - - if (GUILayout.Button("Process Name File") && nameFile != null) - { - var names = new HashSet(); - var lines = nameFile.text.Split('\n'); - foreach (var line in lines) - { - foreach(var word in line.ToUpperInvariant().Split(' ', ',', '.', '"')) - if (word.Length >= minWordLength && !names.Contains(word)) - names.Add(word); - } - Debug.Log($"Found {lines.Length} lines, with {names.Count} unique names!"); - var random = new Random(1337); - _nameGenerator = new MarkovNameGenerator(ref random, names, NameGeneratorOrder, NameGeneratorMinLength, NameGeneratorMaxLength); - } - - if (_nameGenerator != null) - { - if (GUILayout.Button("Generate Name")) - { - Debug.Log(_nameGenerator.NextName); - } - } - } -} \ No newline at end of file diff --git a/Assets/Scripts/Editor/NameTools.cs.meta b/Assets/Scripts/Editor/NameTools.cs.meta deleted file mode 100644 index e0022b308..000000000 --- a/Assets/Scripts/Editor/NameTools.cs.meta +++ /dev/null @@ -1,11 +0,0 @@ -fileFormatVersion: 2 -guid: 88b5f578fe63a904b997331630e61416 -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: diff --git a/Assets/Scripts/Gameplay/GameSettings.cs b/Assets/Scripts/Gameplay/GameSettings.cs index 65e134755..9d89c5822 100644 --- a/Assets/Scripts/Gameplay/GameSettings.cs +++ b/Assets/Scripts/Gameplay/GameSettings.cs @@ -22,7 +22,6 @@ public class GameSettings : ScriptableObject public float MinimapAsteroidSize = 3; public float MinimapIconSize = 1f / 32; public float PlanetRotationSpeed = 1; - public NameGeneratorSettings NameGeneratorSettings; public TutorialGenerationSettings TutorialGenerationSettings; public SectorBackgroundSettings TutorialBackgroundSettings; public SectorBackgroundSettings SectorBackgroundSettings; diff --git a/Assets/Scripts/ServerShared/AetheriaStores.cs b/Assets/Scripts/ServerShared/AetheriaStores.cs index 4697fe272..4afb00b1d 100644 --- a/Assets/Scripts/ServerShared/AetheriaStores.cs +++ b/Assets/Scripts/ServerShared/AetheriaStores.cs @@ -7,7 +7,7 @@ using GameCult.Caching.MessagePack; // Aetheria's stores: one home store per type, composed here and nowhere else. public static class AetheriaStores { - public static readonly Type[] CatalogTypes = { typeof(ItemData), typeof(ShipAuthoring), typeof(Faction), typeof(FactionProductData), typeof(PersonalityAttribute), typeof(NameFile), typeof(InputLayout), typeof(Loadout), typeof(FieldKindData), typeof(VerseGrammar), typeof(VerseVerb) }; + public static readonly Type[] CatalogTypes = { typeof(ItemData), typeof(ShipAuthoring), typeof(Faction), typeof(FactionProductData), typeof(PersonalityAttribute), typeof(InputLayout), typeof(Loadout), typeof(FieldKindData), typeof(VerseGrammar), typeof(VerseVerb) }; public static readonly Type[] RunTypes = { typeof(OrbitData), typeof(BodyData), typeof(SavedZone), typeof(SavedGame), typeof(ProvenanceLedger) }; public static readonly Type[] PlayerTypes = { typeof(PlayerSettings) }; diff --git a/Assets/Scripts/ServerShared/Corporations.cs b/Assets/Scripts/ServerShared/Corporations.cs index 2e18d7199..c242e3e42 100644 --- a/Assets/Scripts/ServerShared/Corporations.cs +++ b/Assets/Scripts/ServerShared/Corporations.cs @@ -37,8 +37,7 @@ public class Faction [InspectableColor, JsonProperty("secondaryColor"), Key(8)] public float3 SecondaryColor; - [Inspectable, JsonProperty("nameFile"), Key(9)] - public CultRecordRef GeonameFile; + // Key 9 was the faction's name-file link, retired by ruling faction-curated-place-names; never reuse it. [Inspectable, JsonProperty("bossHull"), Key(10)] public CultRecordRef BossHull; @@ -70,11 +69,23 @@ public class Faction // tune it in Studio after that. Key 16 is reserved for Relations and 18 for Livery. [Inspectable, JsonProperty("assembly"), Key(19)] public AssemblyDistribution Assembly; + + // Curated place names in authored order; the home zone takes the first (ruling faction-curated-place-names). + // Authored by AetherDb place-names, which restores the table on every apply. + [Inspectable, JsonProperty("places"), Key(21)] + public FactionPlace[] Places = Array.Empty(); } -[CultDocument("aetheria.namefile", "1"), Inspectable, MessagePackObject] -public class NameFile +// Historical, narrative and strategic intent join as keys 2+ when economics-driven placement lands (ruling faction-curated-place-names). +[MessagePackObject, JsonObject(MemberSerialization.OptIn)] +public class FactionPlace { - [CultName, Key(1)] public string Name; - [Key(2)] public string[] Names; + [Inspectable, JsonProperty("name"), Key(1)] + public string Name; + + // The one rule for a valid place name: trimmed, non-empty and not a catalogue designation (EAC-, any case). Galaxy.NamePlaces + // treats an invalid entry as absent; AetherDb place-names refuses a table that holds one. + public static bool IsValidName(string name) => + !string.IsNullOrEmpty(name) && name == name.Trim() && !name.StartsWith("EAC-", StringComparison.OrdinalIgnoreCase); } + diff --git a/Assets/Scripts/ServerShared/Galaxy.cs b/Assets/Scripts/ServerShared/Galaxy.cs index 7026d8a52..0b2f71387 100644 --- a/Assets/Scripts/ServerShared/Galaxy.cs +++ b/Assets/Scripts/ServerShared/Galaxy.cs @@ -20,7 +20,6 @@ public class Galaxy public HashSet DiscoveredZones = new HashSet(); public SectorBackgroundSettings Background { get; } - public NameGeneratorSettings NameGeneratorSettings { get; } public Faction[] Factions { get; } public GalaxyZone[] Zones { get; } public GalaxyZone Entrance { get; } @@ -34,7 +33,6 @@ public class Galaxy public uint Seed { get; } private GalaxyZone[] _exitPath; - private Dictionary _nameGenerators = new Dictionary(); private readonly CultCache _cache; public GalaxyZone[] ExitPath @@ -96,7 +94,6 @@ public class Galaxy public Galaxy( SectorGenerationSettings settings, SectorBackgroundSettings background, - NameGeneratorSettings nameGeneratorSettings, CultCache cache, Action log, Action progressCallback = null, @@ -131,7 +128,7 @@ public class Galaxy CalculateFactionInfluence(progressCallback); - GenerateNames(cache, nameGeneratorSettings, ref random, progressCallback); + NamePlaces(ref random, progressCallback); progressCallback?.Invoke("Done!"); if(progressCallback!=null) Thread.Sleep(500); // Inserting Delay to make it seem like it's doing more work lmao @@ -162,7 +159,6 @@ public class Galaxy public Galaxy( TutorialGenerationSettings settings, SectorBackgroundSettings background, - NameGeneratorSettings nameGeneratorSettings, CultCache cache, PlayerSettings playerSettings, DirectoryInfo narrativeDirectory, @@ -259,7 +255,7 @@ public class Galaxy CalculateFactionInfluence(progressCallback); - GenerateNames(cache, nameGeneratorSettings, ref random, progressCallback); + NamePlaces(ref random, progressCallback); // progressCallback?.Invoke("Weaving Narrative"); // var processor = new StoryProcessor(playerSettings, narrativeDirectory, this, ref random, Log); @@ -350,31 +346,37 @@ public class Galaxy } } - private void GenerateNames(CultCache cache, - NameGeneratorSettings nameGeneratorSettings, - ref Random random, - Action progressCallback = null) + // Each faction names the zones it owns from its own curated list (ruling faction-curated-place-names): nearest its + // home first, so the home zone takes the capital, which is Places[0]. A zone left over when a list is spent, and every + // zone nobody owns, takes a catalogue designation. A name is never used twice, ignoring case. + internal void NamePlaces(ref Random random, Action progressCallback = null) { - var used = new HashSet(); - for (var i = 0; i < Factions.Length; i++) + progressCallback?.Invoke("Naming places"); + var used = new HashSet(StringComparer.OrdinalIgnoreCase); + foreach (var zone in Zones) zone.Name = null; + + foreach (var faction in Factions) { - progressCallback?.Invoke($"Feeding Markov Chains: {i + 1} / {Factions.Length}"); - //if(progressCallback!=null) Thread.Sleep(250); // Inserting Delay to make it seem like it's doing more work lmao - var faction = Factions[i]; - _nameGenerators[faction] = new MarkovNameGenerator(ref random, cache.Get(faction.GeonameFile).Names, nameGeneratorSettings, used); + if (!HomeZones.TryGetValue(faction, out var home)) continue; + var places = (faction.Places ?? Array.Empty()).Select(p => p?.Name).Where(FactionPlace.IsValidName).ToArray(); + var next = 0; + var owned = Zones.Select((zone, index) => (zone, index)).Where(z => z.zone.Owner == faction) + .OrderBy(z => z.zone.Distance[home]).ThenBy(z => z.index); + foreach (var (zone, _) in owned) + { + while (next < places.Length && used.Contains(places[next])) next++; + if (next == places.Length) break; + zone.Name = places[next]; + used.Add(places[next++]); + } } - // Generate zone name using the owner's name generator, otherwise assign catalogue ID - foreach (var zone in Zones) + foreach (var zone in Zones.Where(z => z.Name == null)) { - if (zone.Owner != null) - { - zone.Name = _nameGenerators[zone.Owner].NextName.Trim(); - } - else - { - zone.Name = $"EAC-{random.NextInt(9999).ToString()}"; - } + string designation; + do designation = $"EAC-{random.NextInt(9999)}"; + while (!used.Add(designation)); + zone.Name = designation; } } diff --git a/Assets/Scripts/ServerShared/GalaxyStage.cs b/Assets/Scripts/ServerShared/GalaxyStage.cs index 8b6e5a1a6..cd8feefed 100644 --- a/Assets/Scripts/ServerShared/GalaxyStage.cs +++ b/Assets/Scripts/ServerShared/GalaxyStage.cs @@ -15,7 +15,6 @@ public sealed class GalaxyStage private readonly SectorBackgroundSettings _sectorBackground; private readonly TutorialGenerationSettings _prelude; private readonly SectorBackgroundSettings _preludeBackground; - private readonly NameGeneratorSettings _names; private readonly CultCache _cache; private readonly PlayerSettings _playerSettings; private readonly DirectoryInfo _narrativeDirectory; @@ -28,7 +27,6 @@ public sealed class GalaxyStage SectorBackgroundSettings sectorBackground, TutorialGenerationSettings prelude, SectorBackgroundSettings preludeBackground, - NameGeneratorSettings names, CultCache cache, PlayerSettings playerSettings, DirectoryInfo narrativeDirectory, @@ -40,7 +38,6 @@ public sealed class GalaxyStage _sectorBackground = sectorBackground; _prelude = prelude; _preludeBackground = preludeBackground; - _names = names; _cache = cache; _playerSettings = playerSettings; _narrativeDirectory = narrativeDirectory; @@ -64,7 +61,7 @@ public sealed class GalaxyStage var background = Copy(_sectorBackground); var random = new Random(Seed); background.NoisePosition = random.NextFloat() * 1000; - return new Galaxy(_sector, background, _names, _cache, _log, _progress, Seed); + return new Galaxy(_sector, background, _cache, _log, _progress, Seed); } // A prelude galaxy: the tutorial settings and their fixed factions, centred in dense cloud. @@ -81,7 +78,7 @@ public sealed class GalaxyStage background.NoisePosition = random.NextFloat() * 1000; _progress?.Invoke($"Finding Galaxy Position: iteration {iteration++}"); } while (background.CloudDensity(float2(.5f)) < .5f); - return new Galaxy(cast, background, _names, _cache, _playerSettings, _narrativeDirectory, _log, _progress, Seed); + return new Galaxy(cast, background, _cache, _playerSettings, _narrativeDirectory, _log, _progress, Seed); } private static SectorBackgroundSettings Copy(SectorBackgroundSettings settings) => diff --git a/Assets/Scripts/ServerShared/MarkovNameGenerator.cs b/Assets/Scripts/ServerShared/MarkovNameGenerator.cs deleted file mode 100644 index 528684f40..000000000 --- a/Assets/Scripts/ServerShared/MarkovNameGenerator.cs +++ /dev/null @@ -1,129 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ - -using System; -using System.Collections.Generic; -using System.Linq; -using static CultMath.math; -using Random = CultMath.Random; - -//Generates random names based on the statistical weight of letter sequences -//in a collection of sample names -public class MarkovNameGenerator -{ - private Dictionary> _chains = new Dictionary>(); - private List _samples = new List(); - // The names already given, shared by every generator that must not repeat another's: a galaxy passes one set to - // all its factions' generators. Never process-wide, so one galaxy's names cannot change the next galaxy's. - private readonly HashSet _used; - private int _order; - private int _minLength; - private int _maxLength; - private Random _random; - - public MarkovNameGenerator(ref Random random, IEnumerable sampleNames, NameGeneratorSettings settings, HashSet used) - { - _used = used; - var names = new HashSet(); - foreach (var line in sampleNames) - { - foreach(var word in line.ToUpperInvariant().Split(' ', ',', '.', '"')) - if (word.Length >= settings.NameGeneratorMinLength && !names.Contains(word)) - names.Add(word); - } - - _random = random; - - _order = max(1, settings.NameGeneratorOrder); - _minLength = max(1, settings.NameGeneratorMinLength); - _maxLength = settings.NameGeneratorMaxLength; - - Initialize(names); - } - - public MarkovNameGenerator(ref Random random, IEnumerable sampleNames, int order, int minLength, int maxLength) - { - _used = new HashSet(); - _random = random; - - _order = max(1, order); - _minLength = max(1, minLength); - _maxLength = maxLength; - - Initialize(sampleNames); - } - - private void Initialize(IEnumerable sampleNames) - { - //split comma delimited lines - foreach (string line in sampleNames) - { - string[] tokens = line.Split(' ','\'','(',')'); - foreach (string word in tokens) - { - string upper = word.Trim().ToLowerInvariant(); - if (upper.Length < _order + 1) - continue; - _samples.Add(upper + '|'); // Add end character - } - } - - //Build chains - foreach (string word in _samples) - { - for (int letter = 0; letter < word.Length - _order; letter++) - { - string token = word.Substring(letter, _order); - List entry = null; - if (_chains.ContainsKey(token)) - entry = _chains[token]; - else - { - entry = new List(); - _chains[token] = entry; - } - entry.Add(word[letter + _order]); - } - } - } - - //Get the next random name - public string NextName - { - get - { - - string s = ""; - do - { - int n = _random.NextInt(0,_samples.Count); - //int nameLength = _samples[n].Length; - s = _samples[n].Substring(0, _order);//get a random token at the start of a sample word - while (true)//(s.Length < nameLength) - { - string token = s.Substring(s.Length - _order, _order); - char c = GetLetter(token); - if (c != '?' && c != '|') - s += c;//GetLetter(token); - else - break; - } - s = s.Substring(0, 1).ToUpper() + s.Substring(1); - } - while (_used.Contains(s) || s.Length < _minLength || s.Length > _maxLength); - _used.Add(s); - return s; - } - } - - //Get a random letter from the chain - private char GetLetter(string token) - { - if (!_chains.ContainsKey(token)) - return '?'; - List letters = _chains[token]; - int n = _random.NextInt(0,letters.Count); - return letters[n]; - } -} \ No newline at end of file diff --git a/Assets/Scripts/ServerShared/MarkovNameGenerator.cs.meta b/Assets/Scripts/ServerShared/MarkovNameGenerator.cs.meta deleted file mode 100644 index a0219e5ec..000000000 --- a/Assets/Scripts/ServerShared/MarkovNameGenerator.cs.meta +++ /dev/null @@ -1,11 +0,0 @@ -fileFormatVersion: 2 -guid: 33c3b119ccc9de447b8c8318b6f0d1af -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: diff --git a/Assets/Scripts/ServerShared/Settings.cs b/Assets/Scripts/ServerShared/Settings.cs index b58f223d5..2f329e316 100644 --- a/Assets/Scripts/ServerShared/Settings.cs +++ b/Assets/Scripts/ServerShared/Settings.cs @@ -44,14 +44,6 @@ public class GalaxyShapeSettings public float TwistExponent = 2; } -[Serializable, MessagePackObject(keyAsPropertyName:true), JsonObject] -public class NameGeneratorSettings -{ - public int NameGeneratorMinLength = 5; - public int NameGeneratorMaxLength = 10; - public int NameGeneratorOrder = 4; -} - [Serializable, MessagePackObject(keyAsPropertyName:true), JsonObject] public class SectorBackgroundSettings { diff --git a/Assets/Scripts/UI/MainMenu.cs b/Assets/Scripts/UI/MainMenu.cs index c442c2221..e72ce5263 100644 --- a/Assets/Scripts/UI/MainMenu.cs +++ b/Assets/Scripts/UI/MainMenu.cs @@ -177,7 +177,6 @@ public class MainMenu : MonoBehaviour Settings.SectorBackgroundSettings, Settings.TutorialGenerationSettings, Settings.TutorialBackgroundSettings, - Settings.NameGeneratorSettings, ActionGameManager.CultCache, ActionGameManager.PlayerSettings, ActionGameManager.GameDataDirectory.CreateSubdirectory("Narrative"), diff --git a/GameData/Aetheria.cc b/GameData/Aetheria.cc index f0b68fb9e..9f9ff69e5 100644 --- a/GameData/Aetheria.cc +++ b/GameData/Aetheria.cc @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:2b18ff7f115236eda17252a4a3b01bd2490e684b1722f375b0750e3a70a958d4 -size 12761080 +oid sha256:22342ea360c7f453de500da8650e6a23648f8c1250eb22e724765bbcf619eac0 +size 186037 diff --git a/tests/Aetheria.Shared.Tests/ChildrenWithinReachTests.cs b/tests/Aetheria.Shared.Tests/ChildrenWithinReachTests.cs index ffd50613f..882d60fce 100644 --- a/tests/Aetheria.Shared.Tests/ChildrenWithinReachTests.cs +++ b/tests/Aetheria.Shared.Tests/ChildrenWithinReachTests.cs @@ -40,7 +40,7 @@ public sealed class ChildrenWithinReachTests : IClassFixture("TutorialGenerationSettings"), authored.Read("TutorialBackgroundSettings"), - authored.Read("NameGeneratorSettings"), cache, new PlayerSettings(), + cache, new PlayerSettings(), Directory.CreateDirectory(Path.Combine(dir, "Narrative")), _ => { }, null, seed); var items = new ItemManager(cache, new ProvenanceLedger(), authored.Read("GameplaySettings"), _ => { }); var planetSettings = authored.Read("PlanetSettings"); diff --git a/tests/Aetheria.Shared.Tests/ConsumableSupplyTests.cs b/tests/Aetheria.Shared.Tests/ConsumableSupplyTests.cs index 78cbf6f0f..ea8fbab1e 100644 --- a/tests/Aetheria.Shared.Tests/ConsumableSupplyTests.cs +++ b/tests/Aetheria.Shared.Tests/ConsumableSupplyTests.cs @@ -280,8 +280,7 @@ public sealed class ConsumableSupplyTests : IDisposable var authored = AuthoredSettings.Load(repo); var galaxy = new Galaxy(authored.Read("TutorialGenerationSettings"), - authored.Read("TutorialBackgroundSettings"), authored.Read("NameGeneratorSettings"), - cache, new PlayerSettings(), Directory.CreateDirectory(Path.Combine(_root, "Narrative")), _ => { }, null, 1); + authored.Read("TutorialBackgroundSettings"), cache, new PlayerSettings(), Directory.CreateDirectory(Path.Combine(_root, "Narrative")), _ => { }, null, 1); var items = new ItemManager(cache, new ProvenanceLedger(), authored.Read("GameplaySettings"), _ => { }); int stations = 0, ships = 0; diff --git a/tests/Aetheria.Shared.Tests/FlightTests.cs b/tests/Aetheria.Shared.Tests/FlightTests.cs index ba0a4dd82..b9ee7c09e 100644 --- a/tests/Aetheria.Shared.Tests/FlightTests.cs +++ b/tests/Aetheria.Shared.Tests/FlightTests.cs @@ -910,7 +910,8 @@ public sealed class FlightTests : IDisposable Assert.Equal(turns, turn != 0f); } - // Faction.Doctrine rides at wire slot 17; slot 16 is reserved for Relations. Kills: the key moved. + // Faction.Doctrine rides at wire slot 17; slot 16 is reserved for Relations; Places rides at 21, which sets the array's length. + // Kills: the key moved. [Fact] public void TheFactionDoctrineRidesAtWireSlot17() { @@ -919,7 +920,7 @@ public sealed class FlightTests : IDisposable var slots = MessagePack.MessagePackSerializer.Deserialize(MessagePack.MessagePackSerializer.Serialize(faction, CultDocumentMessagePackSerialization.OptionsFor(typeof(Faction).Assembly))); - Assert.Equal(20, slots.Length); // 19 is Assembly + Assert.Equal(22, slots.Length); // 19 is Assembly, 21 is Places Assert.Null(slots[16]); var doctrine = Assert.IsType(slots[17]); Assert.Equal(6f, Convert.ToSingle(doctrine[1])); diff --git a/tests/Aetheria.Shared.Tests/MiningCut3Tests.cs b/tests/Aetheria.Shared.Tests/MiningCut3Tests.cs index e0eefce59..be506488b 100644 --- a/tests/Aetheria.Shared.Tests/MiningCut3Tests.cs +++ b/tests/Aetheria.Shared.Tests/MiningCut3Tests.cs @@ -814,7 +814,7 @@ public sealed partial class MiningCut3Tests : IDisposable try { galaxy = new Galaxy(RestoredHullsTests.TutorialGalaxySettings(), RestoredHullsTests.TutorialBackgroundSettings(), - RestoredHullsTests.TutorialNameSettings(), cache, new PlayerSettings(), new DirectoryInfo(dir), _ => { }, null, seed); + cache, new PlayerSettings(), new DirectoryInfo(dir), _ => { }, null, seed); } catch (Exception) { continue; } // Galaxy's own seed-dependent flakiness (RestoredHullsTests) var items = new ItemManager(cache, new ProvenanceLedger(), GameSettings(), _ => { }); diff --git a/tests/Aetheria.Shared.Tests/MinorPowersTests.cs b/tests/Aetheria.Shared.Tests/MinorPowersTests.cs index 7416f08df..1988a033e 100644 --- a/tests/Aetheria.Shared.Tests/MinorPowersTests.cs +++ b/tests/Aetheria.Shared.Tests/MinorPowersTests.cs @@ -57,7 +57,7 @@ public sealed class MinorPowersTests : IDisposable foreach (var shortName in Minor) { var faction = Assert.Single(factions, f => f.ShortName == shortName); - Assert.NotNull(cache.Get(faction.GeonameFile)); + Assert.NotEmpty(faction.Places); Assert.True(faction.InfluenceDistance > 0, shortName); var named = faction.Allegiance.Keys.Select(k => k.Key).ToArray(); var expected = factions.Where(f => f != faction && f.ShortName != "Pirates").Select(f => cache.RefOf(f).Key).ToArray(); diff --git a/tests/Aetheria.Shared.Tests/OrbitsFromFieldTests.cs b/tests/Aetheria.Shared.Tests/OrbitsFromFieldTests.cs index 6a3b1c618..66a8e31d8 100644 --- a/tests/Aetheria.Shared.Tests/OrbitsFromFieldTests.cs +++ b/tests/Aetheria.Shared.Tests/OrbitsFromFieldTests.cs @@ -253,7 +253,7 @@ public sealed class OrbitsFromFieldTests : IDisposable var authored = AuthoredSettings.Load(RunStartTests.FindRepoRoot()); var tutorial = authored.Read("TutorialGenerationSettings"); var galaxy = new Galaxy(tutorial, authored.Read("TutorialBackgroundSettings"), - authored.Read("NameGeneratorSettings"), cache, new PlayerSettings(), + cache, new PlayerSettings(), Directory.CreateDirectory(Path.Combine(dir, "Narrative")), _ => { }, null, seed); var items = new ItemManager(cache, new ProvenanceLedger(), authored.Read("GameplaySettings"), _ => { }); var pack = ZoneGenerator.GenerateZone(items, authored.Read("ZoneSettings"), authored.Read("PlanetSettings"), galaxy, galaxy.Entrance, diff --git a/tests/Aetheria.Shared.Tests/PiratesFactionCommandTests.cs b/tests/Aetheria.Shared.Tests/PiratesFactionCommandTests.cs index 0a3cb161d..99ec92249 100644 --- a/tests/Aetheria.Shared.Tests/PiratesFactionCommandTests.cs +++ b/tests/Aetheria.Shared.Tests/PiratesFactionCommandTests.cs @@ -82,7 +82,6 @@ public sealed class PiratesFactionCommandTests : IDisposable var db = AetherDb.Open(root: _root); try { - Assert.Equal("pleiades", db.Cache.Get(faction.GeonameFile).Name); var expected = new (string Design, string Name, string Description)[] { ("Autocannon", "🫳🔫", "Serial number filed off. Still shoots."), @@ -172,17 +171,4 @@ public sealed class PiratesFactionCommandTests : IDisposable Assert.Throws(() => Program.PiratesFaction(apply: true, root: _root)); Assert.Empty(Read().Pirates); } - - [Fact] - public void A_missing_name_file_refuses_and_lands_nothing() - { - var db = AetherDb.Open(catalogWritable: true, root: _root); - var pleiades = db.Cache.GetAll().Single(file => file.Name == "pleiades"); - Assert.True(db.Cache.Remove(db.Cache.RefOf(pleiades).Key)); - db.Cache.FlushAsync().Wait(); - db.Cache.Dispose(); - - Assert.Throws(() => Program.PiratesFaction(apply: true, root: _root)); - Assert.Empty(Read().Pirates); - } } diff --git a/tests/Aetheria.Shared.Tests/PiratesRecordTests.cs b/tests/Aetheria.Shared.Tests/PiratesRecordTests.cs index 637b2e65e..8b8367701 100644 --- a/tests/Aetheria.Shared.Tests/PiratesRecordTests.cs +++ b/tests/Aetheria.Shared.Tests/PiratesRecordTests.cs @@ -25,7 +25,7 @@ public sealed partial class RunStartTests var others = _cache.GetAll().Where(faction => faction != pirates).Select(faction => _cache.RefOf(faction)).ToArray(); Assert.NotEmpty(others); Assert.All(others, other => Assert.True(pirates.Allegiance.ContainsKey(other))); - Assert.NotNull(_cache.Get(pirates.GeonameFile)); + Assert.NotEmpty(pirates.Places); var products = PiratesProducts(); Assert.Equal(8, products.Length); diff --git a/tests/Aetheria.Shared.Tests/PlaceNamesTests.cs b/tests/Aetheria.Shared.Tests/PlaceNamesTests.cs new file mode 100644 index 000000000..9490d049b --- /dev/null +++ b/tests/Aetheria.Shared.Tests/PlaceNamesTests.cs @@ -0,0 +1,366 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text.RegularExpressions; +using GameCult.Caching; +using Xunit; +using Random = CultMath.Random; + +// Each faction names the zones it owns from its own curated list (aetheria-release, cut faction-curated-place-names): the +// home zone takes the capital, the rest follow in distance order, a spent or absent list falls back to catalogue designations, +// and no name is used twice. Galaxies are built the way the game builds them, from the authored settings and the shipped catalog. +public sealed class PlaceNamesTests : IDisposable +{ + private static readonly Regex Designation = new(@"^EAC-\d{1,4}$"); + private static readonly AuthoredSettings Authored = AuthoredSettings.Load(RunStartTests.FindRepoRoot()); + private static readonly CultCache Shared = RestoredHullsTests.OpenCatalog(); + private static readonly Dictionary<(bool, uint), Galaxy> Built = new(); + + private readonly List _own = new(); + + public void Dispose() + { + foreach (var cache in _own) cache.Dispose(); + foreach (var root in _scratch) Directory.Delete(root, true); + } + + private static Galaxy Build(CultCache cache, bool prelude, uint seed) => prelude + ? new Galaxy(Authored.Read("TutorialGenerationSettings"), Authored.Read("TutorialBackgroundSettings"), + cache, new PlayerSettings(), new DirectoryInfo(Path.GetTempPath()), _ => { }, null, seed) + : new Galaxy(Authored.Read("SectorGenerationSettings"), Authored.Read("SectorBackgroundSettings"), + cache, _ => { }, null, seed); + + // A prelude galaxy sometimes refuses to generate for a seed (RestoredHullsTests calls it Galaxy's own seed-dependent + // flakiness); those seeds are skipped, and a test that depends on how many built says so. + private static Galaxy TryBuild(bool prelude, uint seed) + { + lock (Built) + { + if (Built.TryGetValue((prelude, seed), out var known)) return known; + Galaxy galaxy; + try { galaxy = Build(Shared, prelude, seed); } + catch (Exception) { galaxy = null; } + return Built[(prelude, seed)] = galaxy; + } + } + + private static IEnumerable Galaxies(bool prelude, uint last) => + Enumerable.Range(1, (int)last).Select(seed => TryBuild(prelude, (uint)seed)).Where(g => g != null); + + private static Faction[] Placed(Galaxy galaxy) => + galaxy.Factions.Where(f => galaxy.HomeZones.ContainsKey(f) && f.Places.Length != 0).ToArray(); + + private static GalaxyZone[] Owned(Galaxy galaxy, Faction faction) => + galaxy.Zones.Select((zone, index) => (zone, index)).Where(z => z.zone.Owner == faction) + .OrderBy(z => z.zone.Distance[galaxy.HomeZones[faction]]).ThenBy(z => z.index).Select(z => z.zone).ToArray(); + + // Two factions can share a home zone (the prelude puts the quest faction on the buffer's); the zone's owner is the + // nearest home in faction order, and that owner's capital is what the zone is called. + [Fact] + public void TheHomeZoneTakesTheOwnersFirstName() + { + var own = 0; + var shared = 0; + foreach (var galaxy in Galaxies(true, 10).Concat(Galaxies(false, 10))) + foreach (var faction in galaxy.Factions.Where(galaxy.HomeZones.ContainsKey)) + { + var home = galaxy.HomeZones[faction]; + var owner = home.Owner; + Assert.NotNull(owner); + if (owner.Places.Length == 0) Assert.Matches(Designation, home.Name); + else Assert.Equal(owner.Places[0].Name, home.Name); + if (owner == faction) own++; else shared++; + } + Assert.True(own >= 20, $"only {own} factions owned their home"); + Assert.True(shared > 0, "no galaxy shared a home zone, so the shared case was not exercised"); + } + + [Fact] + public void OwnedZonesTakeTheListInDistanceOrder() + { + var checkedFactions = 0; + foreach (var galaxy in Galaxies(true, 10).Concat(Galaxies(false, 10))) + foreach (var faction in Placed(galaxy)) + { + var owned = Owned(galaxy, faction); + var listed = Math.Min(owned.Length, faction.Places.Length); + Assert.Equal(faction.Places.Take(listed).Select(p => p.Name), owned.Take(listed).Select(z => z.Name)); + Assert.All(owned.Skip(listed).Select(z => z.Name), name => Assert.Matches(Designation, name)); + checkedFactions++; + } + Assert.True(checkedFactions >= 20, $"only {checkedFactions} factions were checked"); + } + + [Fact] + public void NoZoneCarriesAnotherFactionsName() + { + var curated = Shared.GetAll().SelectMany(f => f.Places.Select(p => (p.Name, Faction: f))).ToDictionary(t => t.Name, t => t.Faction, StringComparer.OrdinalIgnoreCase); + var zones = 0; + foreach (var galaxy in Galaxies(true, 10).Concat(Galaxies(false, 10))) + foreach (var zone in galaxy.Zones.Where(z => curated.ContainsKey(z.Name))) + { + Assert.True(ReferenceEquals(curated[zone.Name], zone.Owner), + $"zone '{zone.Name}' is owned by '{zone.Owner?.ShortName}' but curated by '{curated[zone.Name].ShortName}'"); + zones++; + } + Assert.True(zones >= 50, $"only {zones} curated zones were checked"); + } + + [Fact] + public void ASpentListFallsBackToCatalogueDesignations() + { + // Its own cache: the faction's list is cut, which must not reach the galaxies the other tests share. + var cache = RestoredHullsTests.OpenCatalog(); + _own.Add(cache); + var galaxy = Enumerable.Range(1, 20).Select(seed => { try { return Build(cache, true, (uint)seed); } catch (Exception) { return null; } }) + .First(g => g != null && Placed(g).Any(f => Owned(g, f).Length >= 4)); + var faction = Placed(galaxy).First(f => Owned(galaxy, f).Length >= 4); + faction.Places = faction.Places.Take(2).ToArray(); + + var random = new Random(7); + galaxy.NamePlaces(ref random); + + var owned = Owned(galaxy, faction); + Assert.Equal(faction.Places.Select(p => p.Name), owned.Take(2).Select(z => z.Name)); + Assert.True(ReferenceEquals(owned[0], galaxy.HomeZones[faction]), $"the nearest zone '{owned[0].Name}' is not {faction.ShortName}'s home '{galaxy.HomeZones[faction].Name}'"); + Assert.All(owned.Skip(2).Select(z => z.Name), name => Assert.Matches(Designation, name)); + } + + // The table refuses a repeated name, but a list that did not come through it (a package's factions) can still hold + // one: the later faction skips a name the earlier one already used, whatever its case. + [Fact] + public void AnotherFactionsNameIsNotReusedWhateverItsCase() + { + var cache = RestoredHullsTests.OpenCatalog(); + _own.Add(cache); + var galaxy = Enumerable.Range(1, 20).Select(seed => { try { return Build(cache, true, (uint)seed); } catch (Exception) { return null; } }) + .First(g => g != null && Placed(g).Count(f => Owned(g, f).Length >= 2) >= 2); + var pair = Placed(galaxy).Where(f => Owned(galaxy, f).Length >= 2).Take(2).ToArray(); + var (first, later) = (pair[0], pair[1]); + Assert.True(Array.IndexOf(galaxy.Factions, first) < Array.IndexOf(galaxy.Factions, later)); + var taken = first.Places[0].Name; + var shouting = taken.ToUpperInvariant(); + Assert.NotEqual(taken, shouting); + // Two consecutive names are taken, so the later faction must skip a run, not a single name. + var takenToo = first.Places[1].Name; + var own = later.Places[0].Name; + later.Places = new[] { new FactionPlace { Name = shouting }, new FactionPlace { Name = takenToo.ToUpperInvariant() }, new FactionPlace { Name = own } }; + + var random = new Random(7); + galaxy.NamePlaces(ref random); + + Assert.Equal(taken, Owned(galaxy, first)[0].Name); + Assert.Equal(own, Owned(galaxy, later)[0].Name); + var names = galaxy.Zones.Select(z => z.Name).ToArray(); + Assert.Equal(names.Length, names.Distinct(StringComparer.OrdinalIgnoreCase).Count()); + } + + // The rule is one function (FactionPlace.IsValidName); an entry that fails it counts as absent, so the next valid name + // is the capital and nothing invalid ever titles a zone. + [Theory] + [InlineData("")] + [InlineData(" ")] + [InlineData(" Padded")] + [InlineData("eac-6")] + [InlineData("EAC-6")] + [InlineData(null)] + public void AnInvalidEntryCountsAsAbsent(string bad) + { + var cache = RestoredHullsTests.OpenCatalog(); + _own.Add(cache); + var galaxy = Enumerable.Range(1, 20).Select(seed => { try { return Build(cache, true, (uint)seed); } catch (Exception) { return null; } }) + .First(g => g != null && Placed(g).Any(f => Owned(g, f).Length >= 3)); + var faction = Placed(galaxy).First(f => Owned(galaxy, f).Length >= 3); + faction.Places = new[] { new FactionPlace { Name = bad }, new FactionPlace { Name = "Real" }, null, new FactionPlace { Name = "Second" } }; + + var random = new Random(7); + galaxy.NamePlaces(ref random); + + Assert.Equal(new[] { "Real", "Second" }, Owned(galaxy, faction).Take(2).Select(z => z.Name)); + Assert.Matches(Designation, Owned(galaxy, faction)[2].Name); + } + + // A faction with no home does not stop the ones after it from naming their zones. + [Fact] + public void AFactionWithoutAHomeDoesNotEndTheNaming() + { + var cache = RestoredHullsTests.OpenCatalog(); + _own.Add(cache); + var galaxy = Enumerable.Range(1, 20).Select(seed => { try { return Build(cache, true, (uint)seed); } catch (Exception) { return null; } }) + .First(g => g != null && Placed(g).Count(f => g.HomeZones[f].Owner == f) >= 2); + var homeless = galaxy.Factions.First(galaxy.HomeZones.ContainsKey); + var later = galaxy.Factions.Where(f => f != homeless && galaxy.HomeZones.ContainsKey(f) && f.Places.Length != 0 && galaxy.HomeZones[f].Owner == f) + .First(f => Array.IndexOf(galaxy.Factions, f) > Array.IndexOf(galaxy.Factions, homeless)); + galaxy.HomeZones.Remove(homeless); + + var random = new Random(7); + galaxy.NamePlaces(ref random); + + Assert.Equal(later.Places[0].Name, galaxy.HomeZones[later].Name); + } + + [Fact] + public void NoTwoZonesShareAName() + { + var checkedGalaxies = 0; + foreach (var galaxy in Galaxies(true, 40).Concat(Galaxies(false, 40))) + { + var names = galaxy.Zones.Select(z => z.Name).ToArray(); + Assert.All(names, name => Assert.False(string.IsNullOrEmpty(name))); + Assert.Equal(names.Length, names.Distinct(StringComparer.OrdinalIgnoreCase).Count()); + checkedGalaxies++; + } + Assert.True(checkedGalaxies >= 40, $"only {checkedGalaxies} galaxies were checked"); + } + + [Fact] + public void AFactionWithoutAListGetsCatalogueDesignations() + { + // Its own cache: the list is cleared, which must not reach the galaxies the other tests share. + var cache = RestoredHullsTests.OpenCatalog(); + _own.Add(cache); + var terris = cache.GetAll().Single(f => f.ShortName == "Miss Terri's"); + Assert.NotEmpty(terris.Places); + var galaxy = Enumerable.Range(1, 20).Select(seed => { try { return Build(cache, true, (uint)seed); } catch (Exception) { return null; } }) + .First(g => g != null && g.Zones.Any(z => z.Owner == terris)); + terris.Places = Array.Empty(); + + var random = new Random(7); + galaxy.NamePlaces(ref random); + + var held = galaxy.Zones.Where(z => z.Owner == terris).ToArray(); + Assert.NotEmpty(held); + Assert.All(held.Select(z => z.Name), name => Assert.Matches(Designation, name)); + } + + [Fact] + public void TheShippedListsAreTheTable() + { + var factions = Shared.GetAll().ToArray(); + foreach (var row in PlaceNamesCommand.Table) + { + var matching = factions.Where(f => Shared.RefOf(f).Key.Value == row.RecordKey).ToArray(); + Assert.True(matching.Length == 1, $"{matching.Length} catalog factions carry the key of the {row.ShortName} row"); + var faction = matching[0]; + Assert.Equal(row.ShortName, faction.ShortName); + Assert.Equal(row.Names, faction.Places.Select(p => p.Name)); + } + Assert.Equal(PlaceNamesCommand.Table.Length, factions.Count(f => f.Places.Length != 0)); + + var names = factions.SelectMany(f => f.Places.Select(p => p.Name)).ToArray(); + Assert.All(names, name => + { + Assert.False(string.IsNullOrEmpty(name)); + Assert.Equal(name.Trim(), name); + Assert.False(name.StartsWith("EAC-", StringComparison.OrdinalIgnoreCase)); + }); + Assert.Equal(names.Length, names.Distinct(StringComparer.OrdinalIgnoreCase).Count()); + } + + // The command's scratch catalog: the shipped one with every list cleared, so an apply has all of it to author. + private string ScratchWithoutLists() + { + var root = Path.Combine(Path.GetTempPath(), "place-names-" + Guid.NewGuid().ToString("N")); + Directory.CreateDirectory(Path.Combine(root, "GameData")); + var path = Path.Combine(root, "GameData", "Aetheria.cc"); + File.Copy(Path.Combine(RunStartTests.FindRepoRoot(), "GameData", "Aetheria.cc"), path); + using (var cache = new CultCache(TestCatalog.Registry())) + { + cache.AddBackingStore(new GameCult.Caching.MessagePack.SingleFileMessagePackBackingStore(path), AetheriaStores.CatalogTypes); + foreach (var faction in cache.GetAll().ToArray()) + { + faction.Places = Array.Empty(); + cache.Commit(batch => batch.Upsert(typeof(Faction), faction, cache.RefOf(faction).Key)); + } + cache.FlushAsync().Wait(); + } + _scratch.Add(root); + return root; + } + + private readonly List _scratch = new(); + private static string CatalogOf(string root) => Path.Combine(root, "GameData", "Aetheria.cc"); + + [Fact] + public void TheCommandIsIdempotentAndRefusesAMismatch() + { + var root = ScratchWithoutLists(); + var before = File.ReadAllBytes(CatalogOf(root)); + Assert.Equal(0, PlaceNamesCommand.Run(new string[0], root)); + Assert.Equal(before, File.ReadAllBytes(CatalogOf(root))); + + Assert.Equal(0, PlaceNamesCommand.Run(new[] { "apply" }, root)); + var landed = File.ReadAllBytes(CatalogOf(root)); + Assert.NotEqual(before, landed); + var db = AetherDb.Open(root: root); + try + { + foreach (var row in PlaceNamesCommand.Table) + Assert.Equal(row.Names, db.Cache.GetAll().Single(f => db.Cache.RefOf(f).Key.Value == row.RecordKey).Places.Select(p => p.Name)); + } + finally { db.Cache.Dispose(); } + Assert.Equal(0, PlaceNamesCommand.Run(new[] { "apply" }, root)); + Assert.Equal(landed, File.ReadAllBytes(CatalogOf(root))); + + // Aya's ShortName changed under its key: identity is the key, so the row no longer describes that faction. + var squatter = AetherDb.Open(catalogWritable: true, root: root); + var aya = squatter.Cache.GetAll().Single(f => f.ShortName == "Aya"); + aya.ShortName = "Ayx"; + squatter.Cache.Commit(batch => batch.Upsert(typeof(Faction), aya, squatter.Cache.RefOf(aya).Key)); + squatter.Cache.Dispose(); + var changed = File.ReadAllBytes(CatalogOf(root)); + var refusal = Assert.Throws(() => PlaceNamesCommand.Run(new[] { "apply" }, root)); + Assert.DoesNotContain("Ayx", refusal.Message); + Assert.Equal(changed, File.ReadAllBytes(CatalogOf(root))); + } + + [Fact] + public void ApplyRestoresAnEditedListOfTheSameLength() + { + var root = ScratchWithoutLists(); + Assert.Equal(0, PlaceNamesCommand.Run(new[] { "apply" }, root)); + var row = PlaceNamesCommand.Table.First(); + var edited = AetherDb.Open(catalogWritable: true, root: root); + var faction = edited.Cache.GetAll().Single(f => edited.Cache.RefOf(f).Key.Value == row.RecordKey); + faction.Places = row.Names.Reverse().Select(n => new FactionPlace { Name = n }).ToArray(); + edited.Cache.Commit(batch => batch.Upsert(typeof(Faction), faction, edited.Cache.RefOf(faction).Key)); + edited.Cache.Dispose(); + + Assert.Equal(0, PlaceNamesCommand.Run(new[] { "apply" }, root)); + + var db = AetherDb.Open(root: root); + try { Assert.Equal(row.Names, db.Cache.GetAll().Single(f => db.Cache.RefOf(f).Key.Value == row.RecordKey).Places.Select(p => p.Name)); } + finally { db.Cache.Dispose(); } + } + + [Theory] + [InlineData("EAC-12")] + [InlineData("eac-12")] + [InlineData(" Padded")] + [InlineData("")] + [InlineData("shunya")] + public void ATableWithABadNameIsRefusedWithNothingWritten(string bad) + { + var root = ScratchWithoutLists(); + var before = File.ReadAllBytes(CatalogOf(root)); + var table = PlaceNamesCommand.Table.Select((row, i) => i == 1 ? row with { List = row.List + " | " + bad } : row).ToArray(); + var refusal = Assert.Throws(() => PlaceNamesCommand.Run(new[] { "apply" }, root, table)); + if (bad.Length != 0) Assert.DoesNotContain(bad, refusal.Message); + Assert.Equal(before, File.ReadAllBytes(CatalogOf(root))); + } + + [Fact] + public void ARowForAKeyTheCatalogDoesNotHoldIsRefusedWithNothingWritten() + { + var root = ScratchWithoutLists(); + var before = File.ReadAllBytes(CatalogOf(root)); + var table = PlaceNamesCommand.Table.Append(new PlaceNamesCommand.Row("00000000-0000-0000-0000-000000000000", "Nobody", "Elsewhere")).ToArray(); + Assert.Throws(() => PlaceNamesCommand.Run(new[] { "apply" }, root, table)); + Assert.Equal(before, File.ReadAllBytes(CatalogOf(root))); + } +} diff --git a/tests/Aetheria.Shared.Tests/RestoredHullsTests.cs b/tests/Aetheria.Shared.Tests/RestoredHullsTests.cs index 6711f894a..ed63994cd 100644 --- a/tests/Aetheria.Shared.Tests/RestoredHullsTests.cs +++ b/tests/Aetheria.Shared.Tests/RestoredHullsTests.cs @@ -91,9 +91,6 @@ public sealed class RestoredHullsTests NoisePosition = 536.5106f, CloudExponent = 10, CloudAmplitude = .01f, }; - internal static NameGeneratorSettings TutorialNameSettings() => - new NameGeneratorSettings { NameGeneratorMinLength = 5, NameGeneratorMaxLength = 10, NameGeneratorOrder = 3 }; - // A station's orbit is a copy of its source planet's own (Parent, Distance) (ZoneGenerator.CreateLagrangeOrbit), // so it always matches that one planet. It matches MORE than one planet only when the source planet shares its // distance with a sibling under the same parent -- exactly what ZoneGenerator's potentialLagrangePoints filter @@ -153,8 +150,7 @@ public sealed class RestoredHullsTests var items = new ItemManager(cache, new ProvenanceLedger(), Settings(), _ => { }); for (var seed = 1u; seed <= 64u; seed++) { - var galaxy = new Galaxy(TutorialGalaxySettings(), TutorialBackgroundSettings(), TutorialNameSettings(), - cache, new PlayerSettings(), new DirectoryInfo(Path.GetTempPath()), _ => { }, null, seed); + var galaxy = new Galaxy(TutorialGalaxySettings(), TutorialBackgroundSettings(), cache, new PlayerSettings(), new DirectoryInfo(Path.GetTempPath()), _ => { }, null, seed); foreach (var zone in galaxy.Zones.Where(z => !ReferenceEquals(z, galaxy.Entrance))) { var orbits = ParentedPlanetOrbits(cache, ZoneGenerator.GenerateZone(items, TutorialZoneSettings(), AuthoredPlanetSettings(), galaxy, zone, isTutorial: false, ambient: false)); @@ -366,7 +362,6 @@ public sealed class RestoredHullsTests var zoneSettings = TutorialZoneSettings(); var tutorialSettings = TutorialGalaxySettings(); var background = TutorialBackgroundSettings(); - var names = TutorialNameSettings(); var gameData = TestCatalog.Repo; var sawGenuineLagrangeOrbit = false; for (uint seed = 1; seed <= 25; seed++) @@ -375,7 +370,7 @@ public sealed class RestoredHullsTests try { using var cache = OpenReadOnlyRealCatalogWithScratchRun(gameData, scratchRun); - var galaxy = new Galaxy(tutorialSettings, background, names, cache, new PlayerSettings(), new DirectoryInfo(Path.GetTempPath()), _ => { }, null, seed); + var galaxy = new Galaxy(tutorialSettings, background, cache, new PlayerSettings(), new DirectoryInfo(Path.GetTempPath()), _ => { }, null, seed); var items = new ItemManager(cache, new ProvenanceLedger(), Settings(), _ => { }); var pack = ZoneGenerator.GenerateZone(items, zoneSettings, AuthoredPlanetSettings(), galaxy, galaxy.Entrance, isTutorial: true); @@ -407,7 +402,6 @@ public sealed class RestoredHullsTests var zoneSettings = TutorialZoneSettings(); var tutorialSettings = TutorialGalaxySettings(); var background = TutorialBackgroundSettings(); - var names = TutorialNameSettings(); var gameData = TestCatalog.Repo; var sawGenuineCandidate = false; for (uint seed = 1; seed <= 25; seed++) @@ -416,7 +410,7 @@ public sealed class RestoredHullsTests try { using var cache = OpenReadOnlyRealCatalogWithScratchRun(gameData, scratchRun); - var galaxy = new Galaxy(tutorialSettings, background, names, cache, new PlayerSettings(), new DirectoryInfo(Path.GetTempPath()), _ => { }, null, seed); + var galaxy = new Galaxy(tutorialSettings, background, cache, new PlayerSettings(), new DirectoryInfo(Path.GetTempPath()), _ => { }, null, seed); var items = new ItemManager(cache, new ProvenanceLedger(), Settings(), _ => { }); var pack = ZoneGenerator.GenerateZone(items, zoneSettings, AuthoredPlanetSettings(), galaxy, galaxy.Entrance, isTutorial: true); @@ -454,8 +448,7 @@ public sealed class RestoredHullsTests try { using var cache = OpenReadOnlyRealCatalogWithScratchRun(gameData, scratchRun); - var galaxy = new Galaxy(TutorialGalaxySettings(), TutorialBackgroundSettings(), TutorialNameSettings(), - cache, new PlayerSettings(), new DirectoryInfo(Path.GetTempPath()), _ => { }, null, seed: 1u); + var galaxy = new Galaxy(TutorialGalaxySettings(), TutorialBackgroundSettings(), cache, new PlayerSettings(), new DirectoryInfo(Path.GetTempPath()), _ => { }, null, seed: 1u); var items = new ItemManager(cache, new ProvenanceLedger(), Settings(), _ => { }); var zoneSettings = TutorialZoneSettings(); @@ -490,8 +483,7 @@ public sealed class RestoredHullsTests try { using var cache = OpenReadOnlyRealCatalogWithScratchRun(gameData, scratchRun); - var galaxy = new Galaxy(TutorialGalaxySettings(), TutorialBackgroundSettings(), TutorialNameSettings(), - cache, new PlayerSettings(), new DirectoryInfo(Path.GetTempPath()), _ => { }, null, seed: 1u); + var galaxy = new Galaxy(TutorialGalaxySettings(), TutorialBackgroundSettings(), cache, new PlayerSettings(), new DirectoryInfo(Path.GetTempPath()), _ => { }, null, seed: 1u); var items = new ItemManager(cache, new ProvenanceLedger(), Settings(), _ => { }); var zoneSettings = TutorialZoneSettings(); @@ -532,7 +524,6 @@ public sealed class RestoredHullsTests var zoneSettings = TutorialZoneSettings(); var tutorialSettings = TutorialGalaxySettings(); var background = TutorialBackgroundSettings(); - var names = TutorialNameSettings(); var gameData = TestCatalog.Repo; var checkedAny = false; @@ -542,7 +533,7 @@ public sealed class RestoredHullsTests try { using var cache = OpenReadOnlyRealCatalogWithScratchRun(gameData, scratchRun); - var galaxy = new Galaxy(tutorialSettings, background, names, cache, new PlayerSettings(), new DirectoryInfo(Path.GetTempPath()), _ => { }, null, seed); + var galaxy = new Galaxy(tutorialSettings, background, cache, new PlayerSettings(), new DirectoryInfo(Path.GetTempPath()), _ => { }, null, seed); var items = new ItemManager(cache, new ProvenanceLedger(), Settings(), _ => { }); var pack = ZoneGenerator.GenerateZone(items, zoneSettings, AuthoredPlanetSettings(), galaxy, galaxy.Entrance, isTutorial: true); @@ -677,8 +668,7 @@ public sealed class RestoredHullsTests // real zone from a real galaxy (e.g. one already known to have no Lagrange candidate) and make it the Entrance. private static Galaxy BuildGalaxyWithForcedEntrance(CultCache cache, uint seed, string entranceZoneName) { - var galaxy = new Galaxy(TutorialGalaxySettings(), TutorialBackgroundSettings(), TutorialNameSettings(), - cache, new PlayerSettings(), new DirectoryInfo(Path.GetTempPath()), _ => { }, null, seed); + var galaxy = new Galaxy(TutorialGalaxySettings(), TutorialBackgroundSettings(), cache, new PlayerSettings(), new DirectoryInfo(Path.GetTempPath()), _ => { }, null, seed); var target = galaxy.Zones.Single(z => z.Name == entranceZoneName); var factions = galaxy.Factions; @@ -789,8 +779,7 @@ public sealed class RestoredHullsTests try { using var cache = OpenReadOnlyRealCatalogWithScratchRun(gameData, scratchRun); - var galaxy = new Galaxy(TutorialGalaxySettings(), TutorialBackgroundSettings(), TutorialNameSettings(), - cache, new PlayerSettings(), new DirectoryInfo(Path.GetTempPath()), _ => { }, null, seed: 1u); + var galaxy = new Galaxy(TutorialGalaxySettings(), TutorialBackgroundSettings(), cache, new PlayerSettings(), new DirectoryInfo(Path.GetTempPath()), _ => { }, null, seed: 1u); var items = new ItemManager(cache, new ProvenanceLedger(), Settings(), _ => { }); int StationCount(GalaxyZone zone, bool isTutorial) => @@ -830,8 +819,7 @@ public sealed class RestoredHullsTests try { using var cache = OpenReadOnlyRealCatalogWithScratchRun(gameData, scratchRun); - var galaxy = new Galaxy(TutorialGalaxySettings(), TutorialBackgroundSettings(), TutorialNameSettings(), - cache, new PlayerSettings(), new DirectoryInfo(Path.GetTempPath()), _ => { }, null, seed: 1u); + var galaxy = new Galaxy(TutorialGalaxySettings(), TutorialBackgroundSettings(), cache, new PlayerSettings(), new DirectoryInfo(Path.GetTempPath()), _ => { }, null, seed: 1u); var items = new ItemManager(cache, new ProvenanceLedger(), Settings(), _ => { }); var pack = ZoneGenerator.GenerateZone(items, TutorialZoneSettings(), AuthoredPlanetSettings(), galaxy, galaxy.Entrance, isTutorial: true); @@ -866,8 +854,7 @@ public sealed class RestoredHullsTests // Must fail if that guard is dropped for real. private static Galaxy BuildGalaxyWithBoostedPresence(CultCache cache, uint seed, string zoneName) { - var galaxy = new Galaxy(TutorialGalaxySettings(), TutorialBackgroundSettings(), TutorialNameSettings(), - cache, new PlayerSettings(), new DirectoryInfo(Path.GetTempPath()), _ => { }, null, seed); + var galaxy = new Galaxy(TutorialGalaxySettings(), TutorialBackgroundSettings(), cache, new PlayerSettings(), new DirectoryInfo(Path.GetTempPath()), _ => { }, null, seed); var target = galaxy.Zones.Single(z => z.Name == zoneName); var factions = galaxy.Factions; diff --git a/tests/Aetheria.Shared.Tests/RunStartTests.cs b/tests/Aetheria.Shared.Tests/RunStartTests.cs index 2ce12782e..efedc20dd 100644 --- a/tests/Aetheria.Shared.Tests/RunStartTests.cs +++ b/tests/Aetheria.Shared.Tests/RunStartTests.cs @@ -51,7 +51,7 @@ public sealed partial class RunStartTests : IDisposable _planetSettings = authored.Read("PlanetSettings"); var tutorial = _tutorialSettings = authored.Read("TutorialGenerationSettings"); _galaxy = new Galaxy(tutorial, authored.Read("TutorialBackgroundSettings"), - authored.Read("NameGeneratorSettings"), _cache, new PlayerSettings(), + _cache, new PlayerSettings(), Directory.CreateDirectory(Path.Combine(_root, "Narrative")), _ => { }, null, GalaxySeed); _items = new ItemManager(_cache, new ProvenanceLedger(), authored.Read("GameplaySettings"), _ => { }); _protagonist = _galaxy.ResolveFaction(tutorial.ProtagonistFaction); @@ -122,7 +122,7 @@ public sealed partial class RunStartTests : IDisposable var authored = AuthoredSettings.Load(FindRepoRoot()); return new Galaxy(authored.Read("SectorGenerationSettings"), authored.Read("SectorBackgroundSettings"), - authored.Read("NameGeneratorSettings"), _cache, _ => { }, null, GalaxySeed); + _cache, _ => { }, null, GalaxySeed); } // Every item a station carries, equipped or stocked, with the product it was made as. diff --git a/tests/Aetheria.Shared.Tests/ScenarioTests.cs b/tests/Aetheria.Shared.Tests/ScenarioTests.cs index 314132459..6be69b91a 100644 --- a/tests/Aetheria.Shared.Tests/ScenarioTests.cs +++ b/tests/Aetheria.Shared.Tests/ScenarioTests.cs @@ -55,7 +55,6 @@ public sealed partial class RunStartTests authored.Read("SectorBackgroundSettings"), authored.Read("TutorialGenerationSettings"), authored.Read("TutorialBackgroundSettings"), - authored.Read("NameGeneratorSettings"), _cache, new PlayerSettings(), Directory.CreateDirectory(Path.Combine(_root, "Narrative")), _ => { }, null, clock); } diff --git a/tests/Aetheria.Shared.Tests/StationPlacementTests.cs b/tests/Aetheria.Shared.Tests/StationPlacementTests.cs index 759e3e83d..dfb2074ad 100644 --- a/tests/Aetheria.Shared.Tests/StationPlacementTests.cs +++ b/tests/Aetheria.Shared.Tests/StationPlacementTests.cs @@ -54,7 +54,7 @@ public sealed class StationPlacementTests : IDisposable var settings = authored.Read("GameplaySettings"); var galaxy = new Galaxy(authored.Read("TutorialGenerationSettings"), authored.Read("TutorialBackgroundSettings"), - authored.Read("NameGeneratorSettings"), cache, new PlayerSettings(), + cache, new PlayerSettings(), Directory.CreateDirectory(Path.Combine(dir, "Narrative")), _ => { }, null, seed); var items = new ItemManager(cache, new ProvenanceLedger(), settings, _ => { }); stock?.Invoke(items); diff --git a/tests/Aetheria.Shared.Tests/SunsNeverGreenTests.cs b/tests/Aetheria.Shared.Tests/SunsNeverGreenTests.cs index 85cbe7ec8..517810c42 100644 --- a/tests/Aetheria.Shared.Tests/SunsNeverGreenTests.cs +++ b/tests/Aetheria.Shared.Tests/SunsNeverGreenTests.cs @@ -183,7 +183,7 @@ public sealed class SunsNeverGreenTests : IDisposable var authored = AuthoredSettings.Load(RunStartTests.FindRepoRoot()); var tutorial = authored.Read("TutorialGenerationSettings"); var galaxy = new Galaxy(tutorial, authored.Read("TutorialBackgroundSettings"), - authored.Read("NameGeneratorSettings"), cache, new PlayerSettings(), + cache, new PlayerSettings(), Directory.CreateDirectory(Path.Combine(dir, "Narrative")), _ => { }, null, seed); var items = new ItemManager(cache, new ProvenanceLedger(), authored.Read("GameplaySettings"), _ => { }); var zoneSettings = authored.Read("ZoneSettings"); diff --git a/tools/AetherDb/MinorPowerFactionsCommand.cs b/tools/AetherDb/MinorPowerFactionsCommand.cs index fbe0a39b6..9853d07ba 100644 --- a/tools/AetherDb/MinorPowerFactionsCommand.cs +++ b/tools/AetherDb/MinorPowerFactionsCommand.cs @@ -19,23 +19,22 @@ public static class MinorPowerFactionsCommand { // Own: the weights this faction names for the other minor powers; every other faction but the Pirates gets Default. // Gain: the weight every existing faction (bar the Pirates and Miss Terri's) gains toward this faction. - // Geonames is the name file the faction draws place names from (question minor-power-geonames: the nearest existing file). internal sealed record Def(string Name, string ShortName, string Description, float3 Primary, float3 Secondary, - string Geonames, int Influence, float Gain, float Default, (string ShortName, float Weight)[] Own); + int Influence, float Gain, float Default, (string ShortName, float Weight)[] Own); internal static readonly Def[] Factions = { new("Aya Collective", "Aya", "A federation of communes, clinics and repair crews. Its equipment comes with the drawings, so the people it protects can keep it running.", - new float3(.72f, .36f, .22f), new float3(.12f, .36f, .25f), "Congo", 3, .3f, .3f, + new float3(.72f, .36f, .22f), new float3(.12f, .36f, .25f), 3, .3f, .3f, new[] { ("Forge", 1f), ("Megiddo", .5f) }), new("Megiddo", "Megiddo", "A defended fleet that holds its boundary until the question is answered. Others buy its protection without sharing its faith.", - new float3(.07f, .09f, .15f), new float3(.90f, .85f, .74f), "pleiades", 4, .3f, .2f, + new float3(.07f, .09f, .15f), new float3(.90f, .85f, .74f), 4, .3f, .2f, new[] { ("Aya", .5f), ("Forge", .5f) }), new("Orbital Forge", "Forge", "Belt engineers who hollow asteroids into homes and yards. Their standards leave room to adapt them, and their drawings travel further than their ships.", - new float3(.42f, .30f, .22f), new float3(.30f, .38f, .52f), "pleiades", 3, .5f, .5f, + new float3(.42f, .30f, .22f), new float3(.30f, .38f, .52f), 3, .5f, .5f, Array.Empty<(string, float)>()), }; @@ -51,17 +50,14 @@ public static class MinorPowerFactionsCommand var apply = args.Contains("apply"); var db = AetherDb.Open(catalogWritable: apply, root: root); var existing = db.Cache.GetAll().ToArray(); - var nameFiles = db.Cache.GetAll().ToArray(); - // Refuse before any write: a name or short name held by a faction under another key, and a missing name file. + // Refuse before any write: a name or short name held by a faction under another key. foreach (var def in Factions) { var key = KeyOf(def); foreach (var other in existing.Where(f => f.Name == def.Name || f.ShortName == def.ShortName)) if (!db.Cache.RefOf(other).Key.Equals(key)) throw new InvalidOperationException("Faction Name or ShortName: held by a faction under another key (conflict)."); - if (nameFiles.Count(n => n.Name == def.Geonames) != 1) - throw new InvalidOperationException("Faction GeonameFile: expected exactly one name file (missing)."); } var minorKeys = Factions.ToDictionary(d => d.ShortName, KeyOf); @@ -80,7 +76,7 @@ public static class MinorPowerFactionsCommand } foreach (var def in created) - Console.WriteLine($"New faction \"{def.Name}\" ({def.ShortName}): influence {def.Influence}, names \"{def.Geonames}\", colours ({def.Primary.x:0.00}, {def.Primary.y:0.00}, {def.Primary.z:0.00}) / ({def.Secondary.x:0.00}, {def.Secondary.y:0.00}, {def.Secondary.z:0.00})\n {def.Description}\n allegiance: {string.Join(", ", def.Own.Select(o => $"{o.ShortName} {o.Weight:0.##}"))} others {def.Default:0.##}"); + Console.WriteLine($"New faction \"{def.Name}\" ({def.ShortName}): influence {def.Influence}, colours ({def.Primary.x:0.00}, {def.Primary.y:0.00}, {def.Primary.z:0.00}) / ({def.Secondary.x:0.00}, {def.Secondary.y:0.00}, {def.Secondary.z:0.00})\n {def.Description}\n allegiance: {string.Join(", ", def.Own.Select(o => $"{o.ShortName} {o.Weight:0.##}"))} others {def.Default:0.##}"); foreach (var (holder, missing) in gains) Console.WriteLine($" {holder.ShortName} gains {string.Join(", ", missing.Select(d => $"{d.ShortName} {d.Gain:0.##}"))}"); @@ -101,7 +97,7 @@ public static class MinorPowerFactionsCommand { Name = def.Name, ShortName = def.ShortName, Description = def.Description, PrimaryColor = def.Primary, SecondaryColor = def.Secondary, - GeonameFile = db.Cache.RefOf(nameFiles.Single(n => n.Name == def.Geonames)), InfluenceDistance = def.Influence, + InfluenceDistance = def.Influence, }; foreach (var reference in everyone) faction.Allegiance[reference] = def.Default; foreach (var other in Factions.Where(o => o != def)) diff --git a/tools/AetherDb/PlaceNamesCommand.cs b/tools/AetherDb/PlaceNamesCommand.cs new file mode 100644 index 000000000..0bb0700b9 --- /dev/null +++ b/tools/AetherDb/PlaceNamesCommand.cs @@ -0,0 +1,126 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using GameCult.Caching; + +// place-names [apply] (aetheria-release, cut faction-curated-place-names; ruling faction-curated-place-names): +// authors every curated faction's Places from the table below, which is the one source of those names. A faction is +// identified by its record key, and its ShortName must agree (ghostlight-verse ruling faction-identity-is-record-key). +// A faction without a row keeps the catalogue designations Galaxy.NamePlaces falls back to. +// Order is meaning: the first name is the faction's capital, and the home zone takes it. Dry run unless passed "apply". +public static class PlaceNamesCommand +{ + // List holds the names separated by " | ", in authored order. + internal sealed record Row(string RecordKey, string ShortName, string List) + { + public string[] Names => List.Split(" | "); + } + + internal static readonly Row[] Table = + { + new("bbd619ed-76c1-464c-829d-3f419cdc2525", "Adrasteia", + "Shunya | Unlisted | Tamas | Silent Address | Maya | The Warm Hour | Nishabda | The Vouch | Akasha | No Such Node | Chhaya | Heat Death Reach | Santulan | Cold Margin | Antara | Without Light | Nirvata | Redacted Deep | Quiet Reach | Last Known"), + new("8443f966-0d88-4ec7-a20e-8edf73fa62f6", "AU", + "Ceres Ramp | Pallas Provisional | Ramp Two | Claimshare | Vesta Works | Hygiea | Bloom Seven | Half Measure | Further Out | Semi-Permanent | Under Construction | Drawing Board | Survey Pending | Ceres Control | Margin of Error | Proof of Concept | Ramp Nine | Good Enough | Ambition | Not Yet Named"), + new("a1f76631-d6d1-443a-9be1-c9b1267d6ca2", "Alakrita", + "Aurea | Celeritas | Elegantia | Via Alakrita | Fatum | Quiet Bay | Laurea | Gloria | Prima Linea | Destiny Yard | Aequor | Corona | Vela | Lumen | Nobilis | Portus Aureus | Inscription | Tessera | Proportio | Decus"), + new("48bbb7537ff9ed0a73c3c353598e8f69", "Aya", + "Hellas | Sanctuary | Common Kitchen | Open Courtyard | Enceladus Commons | Mutual | Assembly Hall | Repair Circle | Refuge Route | Not Inevitable | Free Clinic | Shared Table | Drawings Included | All Welcome | Congress | Keep It Running | Terracotta | Open Door | Freehold Garden | Green Shade"), + // DME's voice is lowercase on purpose. + new("8419f9bc-0167-46ec-86c3-3265d2905fbc", "DME", + "the choir loft | encore | burn it down | turn it up | void communion | last call | margin poem | basement tape | loud room | second encore | feedback | no permit | the pit | spare amp | open mic | after hours | smoke machine | b-side | green room | yara's version"), + new("a9672650-c335-484a-9555-71a35c841aa1", "Ewan Hart", + "Homestead | Second Harvest | Convoy's End | Invented Spring | Grange Hall | Long Furrow | Silo Nine | Good Dog | Feed and Seed | False October | County Line | Still Growing | Tractor Pull | Prairie Bloom | Rain Check | Elevator | Hayloft | Late Frost | Sealed Summer | Old Blue"), + new("1dfaa629-6c63-42a7-91e1-f1ae90f92f09", "Finch", + "Tycho | Serenity Ward | Tranquility | Far Side | Nectaris Clinic | Grace | Crisium | Mireille | Copernicus | Imbrium | Full Coverage | Aitken | Fecunditas | Vaporum | Recovery Ring | Clavius | Earthrise | Second Opinion | Frigoris | Plato"), + new("2f74e6b4-d268-48a9-b2a8-24591c680e84", "Lightsail", + "Mile Zero | Ganymede Stop | Last Anchorage | Dispatch | The Drop Book | Truck Stop | Route Choir | On Time | Callisto Crossing | Haul Road | Weigh Station | Long Haul | Bill of Lading | Overnight | Deadhead | Jake Brake | Signed For | The Line | Breaker Nineteen | Final Mile"), + new("a012e0f4-4b22-47b7-8b24-ff3e1439c949", "Lucent", + "Ishtar Studios | Prime Time | Maxwell Heights | Cloud Nine Live | Channel Aphrodite | Clout | Buzz Market | Ratings Tower | Lagrange Feed | Spotlight | Exclusive | Trending | Broadcast Row | Red Carpet | Season Finale | Sponsored | Headliner | Viral | Golden Hour | Breaking News"), + new("087df3f89e59beab14f9d41c849b202f", "Megiddo", + "First Vigil | Earth Stars | Continuity | The Long Silence | Deferred Ground | Unverified | Boundary Watch | Custody | Not Our World | Pleiades Watch | Second Vault | Presumption | Under Orion | Night Watch | Witness | Flatland Edge | Old Sky | Record Keeper | Patience | Revelation Pending"), + // Real Australian place names, Indigenous-derived and English, a draft Emily may replace (ruling miss-terris-improvised-places). + new("d6466821-6776-4f40-9609-2342debf5c86", "Miss Terri's", + "Woolloomooloo | Wagga Wagga | Humpty Doo | Come By Chance | Yackandandah | Nowhere Else | Kurri Kurri | Lightning Ridge | Mooloolaba | Woy Woy | Banana | Coober Pedy | Gundagai | Burpengary | Toowoomba | Kalgoorlie | Bald Hills | Wollongong | Innaloo | Ballarat"), + new("73d368dd-58a8-4431-b9ba-2d44754d36a1", "NiteLife", + "Caloris Array | Arete Grid | Malam | Cahaya | Graveyard Shift | Lights On | Bintang | Night Count | Pelita | Uptime Pro | Bulan | Grid Plus | Kilat | Afterglow | Load Balance | Peak Hours | Terang | Version Two | Midnight Blue | Keep On"), + new("e34d0354f46873df7ec27273fa2abe3d", "Forge", + "Hollow Pallas | Cinderlace | Stubborn | Open Drawing | Pattern Yard | Bored Through | Slagside | Tolerance | Free Standard | Lathe | Anvil Rock | Copied Twice | Mandrel | Inner Shell | Quench | Forge Light | Billet | Rivet | Blueprint | Worlds Enough"), + // The two emoji are U+1F3F4 and U+1F99C (ruling pirates-record-content). + new("1d8a168def994a76a1ce7cb18f59c038", "Pirates", + "Trojan Nest | Genuine Rescue | Ring Shadow | Patched Cavity | \U0001F3F4 Nobody's | Repainted | Finders Keepers | Hidden Dock | Free Parking | Legit Waystation | New Management | No Questions | Second Hand | False Beacon | Salvage Rights | The Fence | Elegance. Ours. | \U0001F99C Roost | Maintenance Due | Not A Base"), + new("adfb9b08-aabc-45c6-9586-915698a9c4b0", "R&D", + "Prüfstand | Findings | Recertification | Configuration B | Asterisk | Freigabe | Friendly | Sicherheit | Compliance | Revision C | Normal Operation | Abnahme | Test Bench Four | Fine Print | Hold Music | Reassurance | Warranty | Grundlage | Standard Procedure | Kinderleicht"), + new("16bad4f9-7fb5-41c5-8da0-dc76709eb7d3", "Zhestokost", + "Cimmeria | Tharsis Bastion | Olimp | Molot | Valles Forward | Arsenal Commission | Eternal Council | Moneta | Kuznya | Overkill | Assignment Board | Stal | Krepost | Gromada | Campaign | Frontline | Beton | Plamya | Zhelezo | Pobeda"), + }; + + public static int Run(string[] args, string root = null) => Run(args, root, Table); + + // The table is a parameter so a test can hand it a malformed one; production always passes Table. + internal static int Run(string[] args, string root, Row[] table) + { + var apply = args.Contains("apply"); + var db = AetherDb.Open(catalogWritable: apply, root: root); + var factions = db.Cache.GetAll().ToArray(); + var keyOf = factions.ToDictionary(f => db.Cache.RefOf(f).Key.Value, StringComparer.Ordinal); + + // Refuse before any write. A refusal never echoes the offending value. + var seen = new HashSet(StringComparer.OrdinalIgnoreCase); + foreach (var row in table) + { + if (!keyOf.TryGetValue(row.RecordKey, out var faction)) + throw new InvalidOperationException("Place names row: its key is not a faction in the catalog (missing)."); + if (faction.ShortName != row.ShortName) + throw new InvalidOperationException("Place names row: its ShortName differs from the faction under its key (conflict)."); + foreach (var name in row.Names) + { + if (!FactionPlace.IsValidName(name)) + throw new InvalidOperationException("Place names row: a name is empty, untrimmed or a catalogue designation (invalid)."); + if (!seen.Add(name)) + throw new InvalidOperationException("Place names table: a name is repeated (conflict)."); + } + } + + var rows = table.ToDictionary(r => r.RecordKey, StringComparer.Ordinal); + var changed = table.Where(r => !keyOf[r.RecordKey].Places.Select(p => p.Name).SequenceEqual(r.Names)).ToArray(); + + foreach (var faction in factions.OrderBy(f => f.Name, StringComparer.Ordinal)) + { + if (!rows.TryGetValue(db.Cache.RefOf(faction).Key.Value, out var row)) + { + Console.WriteLine($"{faction.Name}: no list: catalogue designations"); + continue; + } + Console.WriteLine($"{faction.Name}: {row.Names.Length} places, capital {row.Names[0]}, {(changed.Contains(row) ? "changed" : "unchanged")}"); + } + if (changed.Length == 0) + { + Console.WriteLine("nothing to do"); + return 0; + } + if (!apply) + { + Console.WriteLine($"\nDry run. Pass \"apply\" to author {changed.Length} place lists."); + return 0; + } + + if (!db.Cache.Commit(batch => + { + foreach (var row in changed) + { + var faction = keyOf[row.RecordKey]; + faction.Places = row.Names.Select(n => new FactionPlace { Name = n }).ToArray(); + batch.Upsert(typeof(Faction), faction, db.Cache.RefOf(faction).Key); + } + })) + throw new InvalidOperationException("The catalog changed while place names were being authored; nothing was written. Run it again."); + Console.WriteLine($"\nAuthored {changed.Length} place lists in Aetheria.cc"); + return 0; + } +} diff --git a/tools/AetherDb/Program.cs b/tools/AetherDb/Program.cs index 79a6d68fa..5df9ed99c 100644 --- a/tools/AetherDb/Program.cs +++ b/tools/AetherDb/Program.cs @@ -58,9 +58,12 @@ public static class Program case "product-lines": return ProductLinesCommand.Run(args.Skip(1).ToArray()); case "minor-power-factions": return MinorPowerFactionsCommand.Run(args.Skip(1).ToArray()); case "assembly-from-maker": return AssemblyFromMakerCommand.Run(AetherDb.Open(catalogWritable: args.Contains("apply")).Cache, args.Contains("apply"), Console.Out); + case "place-names": + Console.OutputEncoding = new System.Text.UTF8Encoding(false); + return PlaceNamesCommand.Run(args.Skip(1).ToArray()); case "doctrine-catalog": return DoctrineCatalog.Run(AetherDb.Open(catalogWritable: args.Contains("apply")).Cache, args.Contains("apply"), Console.Out); default: - Console.WriteLine("commands: census, factions, station-fit, hardpoint-fit, loadout [seed], save, settings, settings-dump, dangling [clear ]... [apply], shield-migrate [apply], brownout-migrate [apply], roles-migrate [apply], firing-arc-migrate [apply], range-falloff [apply] [--effective ] [--end ], targeting-catalog [apply], targeting-catalog-6c [apply], targeting-catalog-6d [apply], ship-authoring create|inspect|validate|compose, field-kinds [apply], mine-launcher [apply], smoke-hull [apply], smoke-rounds [apply], roles-backfill [apply], pd-gear [apply], consumables-first-set [apply], launch-suppressor [apply], pirates-faction [apply], verse-grammar [apply], generic-designs [apply], sensor-stat-set [apply], inspection-scanner [apply], minor-power-factions [apply], product-lines weapons|minor-powers|gear [apply], doctrine-catalog [apply], assembly-from-maker [apply]"); + Console.WriteLine("commands: census, factions, station-fit, hardpoint-fit, loadout [seed], save, settings, settings-dump, dangling [clear ]... [apply], shield-migrate [apply], brownout-migrate [apply], roles-migrate [apply], firing-arc-migrate [apply], range-falloff [apply] [--effective ] [--end ], targeting-catalog [apply], targeting-catalog-6c [apply], targeting-catalog-6d [apply], ship-authoring create|inspect|validate|compose, field-kinds [apply], mine-launcher [apply], smoke-hull [apply], smoke-rounds [apply], roles-backfill [apply], pd-gear [apply], consumables-first-set [apply], launch-suppressor [apply], pirates-faction [apply], verse-grammar [apply], generic-designs [apply], sensor-stat-set [apply], inspection-scanner [apply], minor-power-factions [apply], place-names [apply], product-lines weapons|minor-powers|gear [apply], doctrine-catalog [apply], assembly-from-maker [apply]"); return 1; } } @@ -542,7 +545,6 @@ public static class Program var tutorial = authored.Read("TutorialGenerationSettings"); var background = authored.Read("TutorialBackgroundSettings"); - var names = authored.Read("NameGeneratorSettings"); var zones = authored.Read("ZoneSettings"); var gameplay = authored.Read("GameplaySettings"); @@ -552,7 +554,6 @@ public static class Program $"{tutorial.ZoneCount} zones, link density {tutorial.LinkDensity}"); Console.WriteLine($"background: frequency {background.NoiseFrequency}, amplitude {background.NoiseAmplitude}, " + $"cloud exponent {background.CloudExponent}, density at centre {background.CloudDensity(new float2(.5f)):0.000}"); - Console.WriteLine($"names: order {names.NameGeneratorOrder}, length {names.NameGeneratorMinLength}-{names.NameGeneratorMaxLength}"); Console.WriteLine($"zones: sun mass {zones.SunMass}, planet mass {zones.PlanetMass}, satellite passes {zones.SatellitePasses}, " + $"belt probability {zones.BeltProbability}"); Console.WriteLine($" zone radius curve {zones.ZoneRadius?.Minimum}-{zones.ZoneRadius?.Maximum} exponent {zones.ZoneRadius?.Exponent}"); @@ -694,23 +695,22 @@ public static class Program return false; } - // Each faction's generation-critical links. Galaxy.GenerateNames dereferences the geoname file without a - // guard, so an unset link there stops galaxy generation outright. + // Each faction's generation-critical links and the size of its curated place list. An empty list is not broken: + // Galaxy.NamePlaces falls back to catalogue designations for a faction without one. private static int Factions() { var db = AetherDb.Open(); var products = db.Cache.GetAll().ToArray(); - Console.WriteLine($"{"faction",-26} {"short",-13} {"geonames",-22} {"boss hull",-14} {"influence",-9} {"doctrine",-10} products"); + Console.WriteLine($"{"faction",-26} {"short",-13} {"places",-22} {"boss hull",-14} {"influence",-9} {"doctrine",-10} products"); var broken = 0; foreach (var faction in db.Cache.GetAll().OrderBy(f => f.Name)) { var key = db.Cache.RefOf(faction).Key; - var geonames = faction.GeonameFile.IsSet() ? "set" : "UNSET"; var boss = !faction.BossHull.IsSet() ? "none" : db.Cache.Get(faction.BossHull)?.Name ?? "DANGLING"; - if (geonames == "UNSET" || boss == "DANGLING") broken++; - Console.WriteLine($"{faction.Name,-26} {faction.ShortName,-13} {geonames,-22} {boss,-14} {faction.InfluenceDistance,-9} {(faction.Doctrine == null ? "none" : faction.Doctrine.EngageOn.ToString()),-10} " + + if (boss == "DANGLING") broken++; + Console.WriteLine($"{faction.Name,-26} {faction.ShortName,-13} {faction.Places.Length,-22} {boss,-14} {faction.InfluenceDistance,-9} {(faction.Doctrine == null ? "none" : faction.Doctrine.EngageOn.ToString()),-10} " + products.Count(p => p.Manufacturer.Key.Equals(key))); } Console.WriteLine($"\n{broken} factions carry a link that would break generation"); @@ -2303,9 +2303,6 @@ public static class Program string CodePoints(string text) => string.Join(" ", text.EnumerateRunes().Select(r => $"U+{r.Value:X4}")); - var nameFile = db.Cache.GetAll().FirstOrDefault(n => n.Name == "pleiades"); - if (nameFile == null) throw new InvalidOperationException("No name file named \"pleiades\"."); - var others = db.Cache.GetAll().ToArray(); var pirates = new Faction { @@ -2314,7 +2311,6 @@ public static class Program Description = "A loose coalition of crews and hidden docks moving people and goods outside official control. No command, no common law: possession has a maintenance schedule.", PrimaryColor = new float3(.42f, .40f, .37f), SecondaryColor = new float3(1f, .45f, .05f), - GeonameFile = db.Cache.RefOf(nameFile), InfluenceDistance = 3, }; foreach (var other in AllegianceGaps(db, pirates, others)) pirates.Allegiance[db.Cache.RefOf(other)] = 1f; @@ -2338,7 +2334,7 @@ public static class Program Roles: (design.Roles ?? new List()).Select(role => new ProductRole { Role = role.Name, Mean = PirateProfile.Mean, StandardDeviation = PirateProfile.Dev }).ToList()); }).ToArray(); - Console.WriteLine($"New faction \"{pirates.Name}\": influence {pirates.InfluenceDistance}, names \"{nameFile.Name}\", allegiance 1.0 to {pirates.Allegiance.Count} factions."); + Console.WriteLine($"New faction \"{pirates.Name}\": influence {pirates.InfluenceDistance}, allegiance 1.0 to {pirates.Allegiance.Count} factions."); Console.WriteLine($" colours: primary ({pirates.PrimaryColor.x:0.00}, {pirates.PrimaryColor.y:0.00}, {pirates.PrimaryColor.z:0.00}) primer grey, secondary ({pirates.SecondaryColor.x:0.00}, {pirates.SecondaryColor.y:0.00}, {pirates.SecondaryColor.z:0.00}) hazard orange"); Console.WriteLine($" {pirates.Description}"); Console.WriteLine($"\nNew products ({products.Length}):");