MISC: Add achievements for BN14 (#2140)

This commit is contained in:
catloversg
2025-05-21 04:32:13 +07:00
committed by GitHub
parent f1f7a50721
commit 90555a64e6
24 changed files with 751 additions and 60 deletions

View File

@@ -0,0 +1,69 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
width="256"
height="256"
viewBox="0 0 67.733325 67.733325"
version="1.1"
id="svg5"
inkscape:version="1.4 (86a8ad7, 2024-10-11)"
sodipodi:docname="BN14+.svg"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<sodipodi:namedview
id="namedview7"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageshadow="2"
inkscape:pageopacity="0.0"
inkscape:pagecheckerboard="0"
inkscape:document-units="mm"
showgrid="true"
units="px"
inkscape:current-layer="layer1"
inkscape:showpageshadow="2"
inkscape:deskcolor="#d1d1d1">
<inkscape:grid
type="xygrid"
id="grid1"
originx="0"
originy="0"
spacingy="1"
spacingx="1"
units="px" />
</sodipodi:namedview>
<defs
id="defs2" />
<g
inkscape:groupmode="layer"
id="layer2"
inkscape:label="background">
<rect
style="fill:#000000;stroke:none;stroke-width:7.02745"
id="rect1"
width="67.73333"
height="67.73333"
x="0"
y="0" />
</g>
<g
inkscape:label="main"
inkscape:groupmode="layer"
id="layer1">
<text
xml:space="preserve"
style="font-size:17.6389px;line-height:1.25;font-family:Arial;-inkscape-font-specification:Arial;text-align:center;writing-mode:lr-tb;direction:ltr;text-anchor:middle;fill:#55d400;stroke-width:0.264583"
x="33.591061"
y="40.205647"
id="text1"><tspan
sodipodi:role="line"
id="tspan1"
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:17.6389px;line-height:1.25;font-family:Arial;-inkscape-font-specification:'Arial Bold';text-align:center;text-anchor:middle;fill:#00ff00;fill-opacity:1;stroke-width:0.264583"
x="33.591061"
y="40.205647">BN14+</tspan></text>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.1 KiB

View File

@@ -0,0 +1,69 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
width="256"
height="256"
viewBox="0 0 67.733325 67.733325"
version="1.1"
id="svg5"
inkscape:version="1.4 (86a8ad7, 2024-10-11)"
sodipodi:docname="SF14.1.svg"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<sodipodi:namedview
id="namedview7"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageshadow="2"
inkscape:pageopacity="0.0"
inkscape:pagecheckerboard="0"
inkscape:document-units="mm"
showgrid="true"
units="px"
inkscape:current-layer="layer1"
inkscape:showpageshadow="2"
inkscape:deskcolor="#d1d1d1">
<inkscape:grid
type="xygrid"
id="grid1"
originx="0"
originy="0"
spacingy="1"
spacingx="1"
units="px" />
</sodipodi:namedview>
<defs
id="defs2" />
<g
inkscape:groupmode="layer"
id="layer2"
inkscape:label="background">
<rect
style="fill:#000000;stroke:none;stroke-width:7.02745"
id="rect1"
width="67.73333"
height="67.73333"
x="0"
y="0" />
</g>
<g
inkscape:label="main"
inkscape:groupmode="layer"
id="layer1">
<text
xml:space="preserve"
style="font-size:17.6389px;line-height:1.25;font-family:Arial;-inkscape-font-specification:Arial;text-align:center;writing-mode:lr-tb;direction:ltr;text-anchor:middle;fill:#55d400;stroke-width:0.264583"
x="34.982018"
y="40.175503"
id="text1"><tspan
sodipodi:role="line"
id="tspan1"
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:17.6389px;line-height:1.25;font-family:Arial;-inkscape-font-specification:'Arial Bold';text-align:center;text-anchor:middle;fill:#00ff00;fill-opacity:1;stroke-width:0.264583"
x="34.982018"
y="40.175503">SF14.1</tspan></text>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.1 KiB

View File

@@ -0,0 +1,74 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
width="256"
height="256"
viewBox="0 0 67.733325 67.733325"
version="1.1"
id="svg5"
inkscape:version="1.4 (86a8ad7, 2024-10-11)"
sodipodi:docname="ipvgo-anticheat.svg"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<sodipodi:namedview
id="namedview7"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageshadow="2"
inkscape:pageopacity="0.0"
inkscape:pagecheckerboard="0"
inkscape:document-units="mm"
showgrid="true"
units="px"
inkscape:current-layer="layer1"
inkscape:showpageshadow="2"
inkscape:deskcolor="#d1d1d1">
<inkscape:grid
type="xygrid"
id="grid1"
originx="0"
originy="0"
spacingy="1"
spacingx="1"
units="px" />
</sodipodi:namedview>
<defs
id="defs2" />
<g
inkscape:groupmode="layer"
id="layer2"
inkscape:label="background">
<rect
style="fill:#000000;stroke:none;stroke-width:7.02745"
id="rect1"
width="67.73333"
height="67.73333"
x="0"
y="0" />
</g>
<g
inkscape:label="main"
inkscape:groupmode="layer"
id="layer1">
<text
xml:space="preserve"
style="font-size:13.4056px;line-height:1.25;font-family:Arial;-inkscape-font-specification:Arial;text-align:center;writing-mode:lr-tb;direction:ltr;text-anchor:middle;fill:#55d400;stroke-width:0.264583"
x="33.948486"
y="30.28944"
id="text1"><tspan
sodipodi:role="line"
id="tspan1"
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:13.4056px;line-height:1.25;font-family:Arial;-inkscape-font-specification:'Arial Bold';text-align:center;text-anchor:middle;fill:#00ff00;fill-opacity:1;stroke-width:0.264583"
x="33.948486"
y="30.28944">IPvGO</tspan><tspan
sodipodi:role="line"
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:13.4056px;line-height:1.25;font-family:Arial;-inkscape-font-specification:'Arial Bold';text-align:center;text-anchor:middle;fill:#00ff00;fill-opacity:1;stroke-width:0.264583"
x="33.948486"
y="47.04644"
id="tspan2">Anticheat</tspan></text>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.5 KiB

View File

@@ -0,0 +1,69 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
width="256"
height="256"
viewBox="0 0 67.733325 67.733325"
version="1.1"
id="svg5"
inkscape:version="1.4 (86a8ad7, 2024-10-11)"
sodipodi:docname="ipvgo-winning-streak.svg"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<sodipodi:namedview
id="namedview7"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageshadow="2"
inkscape:pageopacity="0.0"
inkscape:pagecheckerboard="0"
inkscape:document-units="mm"
showgrid="true"
units="px"
inkscape:current-layer="layer1"
inkscape:showpageshadow="2"
inkscape:deskcolor="#d1d1d1">
<inkscape:grid
type="xygrid"
id="grid1"
originx="0"
originy="0"
spacingy="1"
spacingx="1"
units="px" />
</sodipodi:namedview>
<defs
id="defs2" />
<g
inkscape:groupmode="layer"
id="layer2"
inkscape:label="background">
<rect
style="fill:#000000;stroke:none;stroke-width:7.02745"
id="rect1"
width="67.73333"
height="67.73333"
x="0"
y="0" />
</g>
<g
inkscape:label="main"
inkscape:groupmode="layer"
id="layer1">
<text
xml:space="preserve"
style="font-size:17.6389px;line-height:1.25;font-family:Arial;-inkscape-font-specification:Arial;text-align:center;writing-mode:lr-tb;direction:ltr;text-anchor:middle;fill:#55d400;stroke-width:0.264583"
x="33.763317"
y="40.097988"
id="text1"><tspan
sodipodi:role="line"
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:17.6389px;line-height:1.25;font-family:Arial;-inkscape-font-specification:'Arial Bold';text-align:center;text-anchor:middle;fill:#00ff00;fill-opacity:1;stroke-width:0.264583"
x="33.763317"
y="40.097988"
id="tspan3">10 Dan</tspan></text>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.1 KiB

View File

@@ -56,13 +56,13 @@
id="layer1">
<text
xml:space="preserve"
style="font-size:17.6389px;font-family:Arial;-inkscape-font-specification:Arial;text-align:center;writing-mode:lr-tb;direction:ltr;text-anchor:middle;fill:#55d400;stroke-width:0.264583"
style="font-size:17.6389px;line-height:1.25;font-family:Arial;-inkscape-font-specification:Arial;text-align:center;writing-mode:lr-tb;direction:ltr;text-anchor:middle;fill:#55d400;stroke-width:0.264583"
x="33.858055"
y="40.17981"
id="text1"><tspan
sodipodi:role="line"
id="tspan1"
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:17.6389px;font-family:Arial;-inkscape-font-specification:'Arial Bold';text-align:center;text-anchor:middle;fill:#00ff00;fill-opacity:1;stroke-width:0.264583"
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:17.6389px;line-height:1.25;font-family:Arial;-inkscape-font-specification:'Arial Bold';text-align:center;text-anchor:middle;fill:#00ff00;fill-opacity:1;stroke-width:0.264583"
x="33.858055"
y="40.17981">TEXT</tspan></text>
</g>

Before

Width:  |  Height:  |  Size: 2.1 KiB

After

Width:  |  Height:  |  Size: 2.1 KiB

69
dist/icons/achievements/BN14+.svg vendored Normal file
View File

@@ -0,0 +1,69 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
width="256"
height="256"
viewBox="0 0 67.733325 67.733325"
version="1.1"
id="svg5"
inkscape:version="1.4 (86a8ad7, 2024-10-11)"
sodipodi:docname="BN14+.svg"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<sodipodi:namedview
id="namedview7"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageshadow="2"
inkscape:pageopacity="0.0"
inkscape:pagecheckerboard="0"
inkscape:document-units="mm"
showgrid="true"
units="px"
inkscape:current-layer="layer1"
inkscape:showpageshadow="2"
inkscape:deskcolor="#d1d1d1">
<inkscape:grid
type="xygrid"
id="grid1"
originx="0"
originy="0"
spacingy="1"
spacingx="1"
units="px" />
</sodipodi:namedview>
<defs
id="defs2" />
<g
inkscape:groupmode="layer"
id="layer2"
inkscape:label="background">
<rect
style="fill-opacity: 0%;stroke:none;stroke-width:7.02745"
id="rect1"
width="67.73333"
height="67.73333"
x="0"
y="0" />
</g>
<g
inkscape:label="main"
inkscape:groupmode="layer"
id="layer1">
<text
xml:space="preserve"
style="font-size:17.6389px;line-height:1.25;font-family:Arial;-inkscape-font-specification:Arial;text-align:center;writing-mode:lr-tb;direction:ltr;text-anchor:middle;fill:#55d400;stroke-width:0.264583"
x="33.591061"
y="40.205647"
id="text1"><tspan
sodipodi:role="line"
id="tspan1"
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:17.6389px;line-height:1.25;font-family:Arial;-inkscape-font-specification:'Arial Bold';text-align:center;text-anchor:middle;fill:#000000;fill-opacity:1;stroke-width:0.264583"
x="33.591061"
y="40.205647">BN14+</tspan></text>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.1 KiB

69
dist/icons/achievements/SF14.1.svg vendored Normal file
View File

@@ -0,0 +1,69 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
width="256"
height="256"
viewBox="0 0 67.733325 67.733325"
version="1.1"
id="svg5"
inkscape:version="1.4 (86a8ad7, 2024-10-11)"
sodipodi:docname="SF14.1.svg"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<sodipodi:namedview
id="namedview7"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageshadow="2"
inkscape:pageopacity="0.0"
inkscape:pagecheckerboard="0"
inkscape:document-units="mm"
showgrid="true"
units="px"
inkscape:current-layer="layer1"
inkscape:showpageshadow="2"
inkscape:deskcolor="#d1d1d1">
<inkscape:grid
type="xygrid"
id="grid1"
originx="0"
originy="0"
spacingy="1"
spacingx="1"
units="px" />
</sodipodi:namedview>
<defs
id="defs2" />
<g
inkscape:groupmode="layer"
id="layer2"
inkscape:label="background">
<rect
style="fill-opacity: 0%;stroke:none;stroke-width:7.02745"
id="rect1"
width="67.73333"
height="67.73333"
x="0"
y="0" />
</g>
<g
inkscape:label="main"
inkscape:groupmode="layer"
id="layer1">
<text
xml:space="preserve"
style="font-size:17.6389px;line-height:1.25;font-family:Arial;-inkscape-font-specification:Arial;text-align:center;writing-mode:lr-tb;direction:ltr;text-anchor:middle;fill:#55d400;stroke-width:0.264583"
x="34.982018"
y="40.175503"
id="text1"><tspan
sodipodi:role="line"
id="tspan1"
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:17.6389px;line-height:1.25;font-family:Arial;-inkscape-font-specification:'Arial Bold';text-align:center;text-anchor:middle;fill:#000000;fill-opacity:1;stroke-width:0.264583"
x="34.982018"
y="40.175503">SF14.1</tspan></text>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.1 KiB

View File

@@ -0,0 +1,74 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
width="256"
height="256"
viewBox="0 0 67.733325 67.733325"
version="1.1"
id="svg5"
inkscape:version="1.4 (86a8ad7, 2024-10-11)"
sodipodi:docname="ipvgo-anticheat.svg"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<sodipodi:namedview
id="namedview7"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageshadow="2"
inkscape:pageopacity="0.0"
inkscape:pagecheckerboard="0"
inkscape:document-units="mm"
showgrid="true"
units="px"
inkscape:current-layer="layer1"
inkscape:showpageshadow="2"
inkscape:deskcolor="#d1d1d1">
<inkscape:grid
type="xygrid"
id="grid1"
originx="0"
originy="0"
spacingy="1"
spacingx="1"
units="px" />
</sodipodi:namedview>
<defs
id="defs2" />
<g
inkscape:groupmode="layer"
id="layer2"
inkscape:label="background">
<rect
style="fill-opacity: 0%;stroke:none;stroke-width:7.02745"
id="rect1"
width="67.73333"
height="67.73333"
x="0"
y="0" />
</g>
<g
inkscape:label="main"
inkscape:groupmode="layer"
id="layer1">
<text
xml:space="preserve"
style="font-size:13.4056px;line-height:1.25;font-family:Arial;-inkscape-font-specification:Arial;text-align:center;writing-mode:lr-tb;direction:ltr;text-anchor:middle;fill:#55d400;stroke-width:0.264583"
x="33.948486"
y="30.28944"
id="text1"><tspan
sodipodi:role="line"
id="tspan1"
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:13.4056px;line-height:1.25;font-family:Arial;-inkscape-font-specification:'Arial Bold';text-align:center;text-anchor:middle;fill:#000000;fill-opacity:1;stroke-width:0.264583"
x="33.948486"
y="30.28944">IPvGO</tspan><tspan
sodipodi:role="line"
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:13.4056px;line-height:1.25;font-family:Arial;-inkscape-font-specification:'Arial Bold';text-align:center;text-anchor:middle;fill:#000000;fill-opacity:1;stroke-width:0.264583"
x="33.948486"
y="47.04644"
id="tspan2">Anticheat</tspan></text>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.5 KiB

View File

@@ -0,0 +1,69 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
width="256"
height="256"
viewBox="0 0 67.733325 67.733325"
version="1.1"
id="svg5"
inkscape:version="1.4 (86a8ad7, 2024-10-11)"
sodipodi:docname="ipvgo-winning-streak.svg"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<sodipodi:namedview
id="namedview7"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageshadow="2"
inkscape:pageopacity="0.0"
inkscape:pagecheckerboard="0"
inkscape:document-units="mm"
showgrid="true"
units="px"
inkscape:current-layer="layer1"
inkscape:showpageshadow="2"
inkscape:deskcolor="#d1d1d1">
<inkscape:grid
type="xygrid"
id="grid1"
originx="0"
originy="0"
spacingy="1"
spacingx="1"
units="px" />
</sodipodi:namedview>
<defs
id="defs2" />
<g
inkscape:groupmode="layer"
id="layer2"
inkscape:label="background">
<rect
style="fill-opacity: 0%;stroke:none;stroke-width:7.02745"
id="rect1"
width="67.73333"
height="67.73333"
x="0"
y="0" />
</g>
<g
inkscape:label="main"
inkscape:groupmode="layer"
id="layer1">
<text
xml:space="preserve"
style="font-size:17.6389px;line-height:1.25;font-family:Arial;-inkscape-font-specification:Arial;text-align:center;writing-mode:lr-tb;direction:ltr;text-anchor:middle;fill:#55d400;stroke-width:0.264583"
x="33.763317"
y="40.097988"
id="text1"><tspan
sodipodi:role="line"
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:17.6389px;line-height:1.25;font-family:Arial;-inkscape-font-specification:'Arial Bold';text-align:center;text-anchor:middle;fill:#000000;fill-opacity:1;stroke-width:0.264583"
x="33.763317"
y="40.097988"
id="tspan3">10 Dan</tspan></text>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.1 KiB

View File

@@ -131,6 +131,11 @@
"Name": "They're lunatics",
"Description": "Acquire SF13.1"
},
"SF14.1": {
"ID": "SF14.1",
"Name": "IPvGO Subnet Takeover",
"Description": "Acquire SF14.1"
},
"MONEY_1Q": {
"ID": "MONEY_1Q",
"Name": "Here comes the money!",
@@ -391,6 +396,16 @@
"Name": "More BitNodes, please!",
"Description": "Reach SF x.3 in each BitNode."
},
"IPVGO_ANTICHEAT": {
"ID": "IPVGO_ANTICHEAT",
"Name": "IPvGO anticheat",
"Description": "Fail to cheat an IPvGO game and be ejected from the subnet."
},
"IPVGO_WINNING_STREAK": {
"ID": "IPVGO_WINNING_STREAK",
"Name": "Ten Steps Ahead",
"Description": "Get a winning streak of 10 against Illuminati."
},
"CHALLENGE_BN1": {
"ID": "CHALLENGE_BN1",
"Name": "BN1: Challenge",
@@ -436,6 +451,16 @@
"Name": "BN12: Challenge",
"Description": "Destroy BN12 50 times."
},
"CHALLENGE_BN13": {
"ID": "CHALLENGE_BN13",
"Name": "BN13: Challenge",
"Description": "Complete BN13 without Stanek's Gift."
},
"CHALLENGE_BN14": {
"ID": "CHALLENGE_BN14",
"Name": "BN14: Challenge",
"Description": "Destroy BN14 without making a move or cheating via IPvGO APIs."
},
"BYPASS": {
"ID": "BYPASS",
"Name": "Exploit: bypass",
@@ -481,11 +506,6 @@
"Name": "UNACHIEVABLE",
"Description": "This achievement cannot be unlocked."
},
"CHALLENGE_BN13": {
"ID": "CHALLENGE_BN13",
"Name": "BN13: Challenge",
"Description": "Complete BN13 without Stanek's Gift."
},
"DEVMENU": {
"ID": "DEVMENU",
"Name": "Exploit: you're not meant to access this",

View File

@@ -29,16 +29,51 @@ import { workerScripts } from "../Netscript/WorkerScripts";
import { getRecordValues } from "../Types/Record";
import { ServerConstants } from "../Server/data/Constants";
import { canAccessBitNodeFeature, isBitNodeFinished, knowAboutBitverse, validBitNodes } from "../BitNode/BitNodeUtils";
import { canAccessBitNodeFeature, isBitNodeFinished, knowAboutBitverse } from "../BitNode/BitNodeUtils";
import { validBitNodes } from "../BitNode/Constants";
import { isLegacyScript } from "../Paths/ScriptFilePath";
import { Settings } from "../Settings/Settings";
import { activateSteamAchievements } from "../Electron";
import { Go } from "../Go/Go";
import { type AchievementId, type SFAchievementId, SFAchievementIds } from "./Types";
// Unable to correctly cast the JSON data into AchievementDataJson type otherwise...
const achievementData = (<AchievementDataJson>(<unknown>data)).achievements;
function assertAchievements(
achievements: typeof data.achievements,
): asserts achievements is AchievementDataJson["achievements"] {
for (const [key, value] of Object.entries(achievements)) {
if (key !== value.ID) {
throw new Error(`Invalid achievement ID. Key: ${key}. Value: ${value.ID}`);
}
}
}
/**
* The type of data.achievements is:
{
CYBERSEC: {
ID: string;
Name: string;
Description: string;
};
NITESEC: {
ID: string;
Name: string;
Description: string;
};
...
}
* However, we want:
* - Typechecking at compile time: ID must be AchievementId, not string.
* - Runtime check: The value of ID must be the same as the key of the achievement. For example, with "CYBERSEC"
* achievement, the key is "CYBERSEC", so its ID must also be "CYBERSEC".
*
* We use assertAchievements to do the runtime check and assert the type.
*/
const achievementData = data.achievements;
assertAchievements(achievementData);
export interface Achievement {
ID: string;
ID: AchievementId;
Icon?: string;
Name?: string;
Description?: string;
@@ -46,48 +81,59 @@ export interface Achievement {
NotInSteam?: boolean;
Condition: () => boolean;
Visible?: () => boolean;
AdditionalUnlock?: string[]; // IDs of achievements that should be awarded when awarding this one
AdditionalUnlock?: AchievementId[]; // IDs of achievements that should be awarded when awarding this one
}
export interface PlayerAchievement {
ID: string;
ID: AchievementId;
unlockedOn?: number;
}
export interface AchievementDataJson {
achievements: Record<string, AchievementData>;
achievements: Record<AchievementId, AchievementData>;
}
export interface AchievementData {
ID: string;
ID: AchievementId;
Name: string;
Description: string;
}
function sfAchievements(): Record<string, Achievement> {
const achievements: Record<string, Achievement> = {};
for (let i = 1; i <= 13; i++) {
const ID = `SF${i}.1`;
achievements[ID] = {
...achievementData[ID],
Icon: ID,
function sfAchievements(): Record<SFAchievementId, Achievement> {
const achievements = {} as Record<SFAchievementId, Achievement>;
for (const id of SFAchievementIds) {
const matchResult = id.match(/SF(\d{1,2})\.1/);
if (!matchResult) {
throw new Error(`Unexpected SFAchievementId: ${id}`);
}
const bn = Number.parseInt(matchResult[1]);
if (!validBitNodes.includes(bn)) {
throw new Error(`Unexpected BN value in SFAchievementId: ${id}`);
}
achievements[id] = {
/**
* The type of achievementData is still the original type (CYBERSEC: { ID: string; Name: string; Description: string; }).
* We have to typecast it here.
*/
...(achievementData as AchievementDataJson["achievements"])[id],
Icon: id,
Visible: knowAboutBitverse,
Condition: () => Player.sourceFileLvl(i) >= 1,
NotInSteam: i >= 13,
Condition: () => Player.sourceFileLvl(bn) >= 1,
NotInSteam: bn >= 13,
};
}
return achievements;
}
export const achievements: Record<string, Achievement> = {
[FactionName.CyberSec.toUpperCase()]: {
...achievementData[FactionName.CyberSec.toUpperCase()],
export const achievements: Record<AchievementId, Achievement> = {
CYBERSEC: {
...achievementData.CYBERSEC,
Icon: "CSEC",
Condition: () => Player.factions.includes(FactionName.CyberSec),
},
[FactionName.NiteSec.toUpperCase()]: {
...achievementData[FactionName.NiteSec.toUpperCase()],
Icon: FactionName.NiteSec,
NITESEC: {
...achievementData.NITESEC,
Icon: "NiteSec",
Condition: () => Player.factions.includes(FactionName.NiteSec),
},
THE_BLACK_HAND: {
@@ -95,24 +141,24 @@ export const achievements: Record<string, Achievement> = {
Icon: "TBH",
Condition: () => Player.factions.includes(FactionName.TheBlackHand),
},
[FactionName.BitRunners.toUpperCase()]: {
...achievementData[FactionName.BitRunners.toUpperCase()],
Icon: FactionName.BitRunners.toLowerCase(),
BITRUNNERS: {
...achievementData.BITRUNNERS,
Icon: "bitrunners",
Condition: () => Player.factions.includes(FactionName.BitRunners),
},
[FactionName.Daedalus.toUpperCase()]: {
...achievementData[FactionName.Daedalus.toUpperCase()],
Icon: FactionName.Daedalus.toLowerCase(),
DAEDALUS: {
...achievementData.DAEDALUS,
Icon: "daedalus",
Condition: () => Player.factions.includes(FactionName.Daedalus),
},
THE_COVENANT: {
...achievementData.THE_COVENANT,
Icon: FactionName.TheCovenant.toLowerCase().replace(/ /g, ""),
Icon: "thecovenant",
Condition: () => Player.factions.includes(FactionName.TheCovenant),
},
[FactionName.Illuminati.toUpperCase()]: {
...achievementData[FactionName.Illuminati.toUpperCase()],
Icon: FactionName.Illuminati.toLowerCase(),
ILLUMINATI: {
...achievementData.ILLUMINATI,
Icon: "illuminati",
Condition: () => Player.factions.includes(FactionName.Illuminati),
},
"BRUTESSH.EXE": {
@@ -522,6 +568,20 @@ export const achievements: Record<string, Achievement> = {
Condition: () => validBitNodes.every((bn) => Player.sourceFileLvl(bn) >= 3),
NotInSteam: true,
},
IPVGO_ANTICHEAT: {
...achievementData.IPVGO_ANTICHEAT,
Icon: "ipvgo-anticheat",
Visible: knowAboutBitverse,
Condition: () => false,
NotInSteam: true,
},
IPVGO_WINNING_STREAK: {
...achievementData.IPVGO_WINNING_STREAK,
Icon: "ipvgo-winning-streak",
Visible: knowAboutBitverse,
Condition: () => false,
NotInSteam: true,
},
CHALLENGE_BN1: {
...achievementData.CHALLENGE_BN1,
Icon: "BN1+",
@@ -596,6 +656,22 @@ export const achievements: Record<string, Achievement> = {
Visible: () => canAccessBitNodeFeature(12),
Condition: () => Player.sourceFileLvl(12) >= 50,
},
CHALLENGE_BN13: {
...achievementData.CHALLENGE_BN13,
Icon: "BN13+",
Visible: () => canAccessBitNodeFeature(13),
Condition: () =>
Player.bitNodeN === 13 &&
isBitNodeFinished() &&
!Player.augmentations.some((a) => a.name === AugmentationName.StaneksGift1),
},
CHALLENGE_BN14: {
...achievementData.CHALLENGE_BN14,
Icon: "BN14+",
Visible: knowAboutBitverse,
Condition: () => Player.bitNodeN === 14 && isBitNodeFinished() && !Go.moveOrCheatViaApi,
NotInSteam: true,
},
BYPASS: {
...achievementData.BYPASS,
Icon: "SF-1",
@@ -651,15 +727,6 @@ export const achievements: Record<string, Achievement> = {
// Hey Players! Yes, you're supposed to modify this to get the achievement!
Condition: () => false,
},
CHALLENGE_BN13: {
...achievementData.CHALLENGE_BN13,
Icon: "BN13+",
Visible: () => canAccessBitNodeFeature(13),
Condition: () =>
Player.bitNodeN === 13 &&
isBitNodeFinished() &&
!Player.augmentations.some((a) => a.name === AugmentationName.StaneksGift1),
},
DEVMENU: {
...achievementData.DEVMENU,
Icon: "SF-1",

View File

@@ -23,5 +23,7 @@ Steps:
- Match the order of achievements in `AchievementData.json`.
- `Icon` must be the name of the .svg file.
- `NotInSteam` must be true.
- Run `pack-for-web.sh`.
- Run `pack-for-web.sh` in `$PROJECT_DIR/assets/Steam/achievements`.
- When committing, remember to commit the changes in `$PROJECT_DIR/dist/icons/achievements`.
Note: If you add a new SFx.1 achievement, you must add its ID to `SFAchievementIds` in `$PROJECT_DIR/src/Achievements/Types.ts`.

20
src/Achievements/Types.ts Normal file
View File

@@ -0,0 +1,20 @@
import data from "./AchievementData.json";
export type AchievementId = keyof typeof data.achievements;
export const SFAchievementIds = [
"SF1.1",
"SF2.1",
"SF3.1",
"SF4.1",
"SF5.1",
"SF6.1",
"SF7.1",
"SF8.1",
"SF9.1",
"SF10.1",
"SF11.1",
"SF12.1",
"SF13.1",
"SF14.1",
] as const;
export type SFAchievementId = (typeof SFAchievementIds)[number];

View File

@@ -4,8 +4,7 @@ import { GetServer } from "../Server/AllServers";
import { Server } from "../Server/Server";
import { SpecialServers } from "../Server/data/SpecialServers";
import { JSONMap } from "../Types/Jsonable";
export const validBitNodes = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14];
import { validBitNodes } from "./Constants";
export function isBitNodeFinished(): boolean {
const wd = GetServer(SpecialServers.WorldDaemon);

1
src/BitNode/Constants.ts Normal file
View File

@@ -0,0 +1 @@
export const validBitNodes = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14];

View File

@@ -14,11 +14,12 @@ import LockOpenIcon from "@mui/icons-material/LockOpen";
import { Player } from "@player";
import { achievements } from "../../Achievements/Achievements";
import { Engine } from "../../engine";
import type { AchievementId } from "../../Achievements/Types";
export function AchievementsDev(): React.ReactElement {
const [playerAchievement, setPlayerAchievements] = useState(Player.achievements.map((m) => m.ID));
function grantAchievement(id: string): void {
function grantAchievement(id: AchievementId): void {
Player.giveAchievement(id);
setPlayerAchievements(Player.achievements.map((m) => m.ID));
}
@@ -28,7 +29,7 @@ export function AchievementsDev(): React.ReactElement {
setPlayerAchievements(Player.achievements.map((m) => m.ID));
}
function removeAchievement(id: string): void {
function removeAchievement(id: AchievementId): void {
Player.achievements = Player.achievements.filter((a) => a.ID !== id);
setPlayerAchievements(Player.achievements.map((m) => m.ID));
}

View File

@@ -16,6 +16,8 @@ export class GoObject {
currentGame: BoardState = getNewBoardState(7);
stats: PartialRecord<GoOpponent, OpponentStats> = {};
storedCycles: number = 0;
// This flag is used when checking the achievement CHALLENGE_BN14.
moveOrCheatViaApi = false;
prestigeAugmentation() {
for (const opponent of getRecordKeys(Go.stats)) {
@@ -36,6 +38,7 @@ export class GoObject {
this.previousGame = null;
this.currentGame = getNewBoardState(7);
this.stats = {};
this.moveOrCheatViaApi = false;
resetGoPromises();
}

View File

@@ -24,6 +24,7 @@ type SaveFormat = {
currentGame: CurrentGameSaveData;
stats: PartialRecord<GoOpponent, OpponentStats>;
storedCycles: number;
moveOrCheatViaApi: boolean;
};
export function getGoSave(): SaveFormat {
@@ -46,6 +47,7 @@ export function getGoSave(): SaveFormat {
},
stats: Go.stats,
storedCycles: Go.storedCycles,
moveOrCheatViaApi: Go.moveOrCheatViaApi,
};
}
@@ -85,6 +87,9 @@ export function loadGo(data: unknown): boolean {
Go.previousGame = previousGame;
Go.stats = stats;
Go.storeCycles(loadStoredCycles(parsedData.storedCycles));
if (typeof parsedData.moveOrCheatViaApi === "boolean") {
Go.moveOrCheatViaApi = parsedData.moveOrCheatViaApi;
}
resetAI();
handleNextTurn(currentGame).catch((error) => {

View File

@@ -1,7 +1,7 @@
import type { Board, BoardState, PointState } from "../Types";
import { Player } from "@player";
import { GoOpponent, GoColor } from "@enums";
import { GoOpponent, GoColor, FactionName } from "@enums";
import { newOpponentStats } from "../Constants";
import { getAllChains, getPlayerNeighbors } from "./boardAnalysis";
import { getKomi, resetAI } from "./goAI";
@@ -77,6 +77,10 @@ export function endGoGame(boardState: BoardState) {
Factions[factionName].setFavor(newFavor);
statusToUpdate.rep += repToAdd;
}
if (factionName === FactionName.Illuminati && statusToUpdate.winStreak >= 10) {
Player.giveAchievement("IPVGO_WINNING_STREAK");
}
}
statusToUpdate.nodePower +=

View File

@@ -29,6 +29,7 @@ import { newOpponentStats } from "../Constants";
* Check the move based on the current settings
*/
export function validateMove(error: (s: string) => never, x: number, y: number, methodName = "", settings = {}): void {
Go.moveOrCheatViaApi = true;
const check = {
emptyNode: true,
requireNonEmptyNode: false,
@@ -502,10 +503,11 @@ export function determineCheatSuccess(
if ((successRngOverride ?? rng.random()) <= cheatSuccessChance(state.cheatCount, playAsWhite)) {
callback();
}
// If there have been prior cheat attempts, and the cheat fails, there is a 10% chance of instantly losing
// If there have been prior cheat attempts, and the cheat fails, there is a 10% chance of instantly ending the game
else if (priorCheatCount && (ejectRngOverride ?? rng.random()) < 0.1 && state.ai !== GoOpponent.none) {
logger(`Cheat failed! You have been ejected from the subnet.`);
forceEndGoGame(state);
Player.giveAchievement("IPVGO_ANTICHEAT");
return handleNextTurn(state, true);
} else {
// If the cheat fails, your turn is skipped

View File

@@ -108,7 +108,8 @@ import { ServerConstants } from "./Server/data/Constants";
import { assertFunctionWithNSContext } from "./Netscript/TypeAssertion";
import { Router } from "./ui/GameRoot";
import { Page } from "./ui/Router";
import { canAccessBitNodeFeature, validBitNodes } from "./BitNode/BitNodeUtils";
import { canAccessBitNodeFeature } from "./BitNode/BitNodeUtils";
import { validBitNodes } from "./BitNode/Constants";
import { isIPAddress } from "./Types/strings";
import { compile } from "./NetscriptJSEvaluator";
import { Script } from "./Script/Script";

View File

@@ -48,7 +48,7 @@ import { ServerConstants } from "../Server/data/Constants";
import { blackOpsArray } from "../Bladeburner/data/BlackOperations";
import { calculateEffectiveRequiredReputation } from "../Company/utils";
import { addRepToFavor } from "../Faction/formulas/favor";
import { validBitNodes } from "../BitNode/BitNodeUtils";
import { validBitNodes } from "../BitNode/Constants";
import { exceptionAlert } from "../utils/helpers/exceptionAlert";
import { cat } from "../Terminal/commands/cat";
import { Crimes } from "../Crime/Crimes";

View File

@@ -54,6 +54,7 @@ import { AlertEvents } from "../../ui/React/AlertManager";
import { Augmentations } from "../../Augmentation/Augmentations";
import { PlayerEventType, PlayerEvents } from "./PlayerEvents";
import { Result } from "../../types";
import type { AchievementId } from "../../Achievements/Types";
export function init(this: PlayerObject): void {
/* Initialize Player's home computer */
@@ -561,9 +562,11 @@ export function giveExploit(this: PlayerObject, exploit: Exploit): void {
}
}
export function giveAchievement(this: PlayerObject, achievementId: string): void {
export function giveAchievement(this: PlayerObject, achievementId: AchievementId): void {
const achievement = achievements[achievementId];
if (!achievement) return;
if (!achievement) {
return;
}
if (!this.achievements.map((a) => a.ID).includes(achievementId)) {
this.achievements.push({ ID: achievementId, unlockedOn: new Date().getTime() });
SnackbarEvents.emit(`Unlocked Achievement: "${achievement.Name}"`, ToastVariant.SUCCESS, 2000);

View File

@@ -45,6 +45,7 @@ exports[`Check Save File Continuity GoSave continuity 1`] = `
"previousBoard": "",
"previousPlayer": "White",
},
"moveOrCheatViaApi": false,
"previousGame": null,
"stats": {},
"storedCycles": 0,