From fb5e67924d45441dbe8ced0c28e0e6b4c29228d2 Mon Sep 17 00:00:00 2001 From: Corey Sotiropoulos Date: Mon, 15 Apr 2024 12:05:27 -0400 Subject: [PATCH] Improve settings display --- .devcontainer/docker-compose.yml | 2 + api/api/urls.py | 7 +- .../src/data => api}/defaultLsbSettings.json | 0 api/docker-compose.yml | 16 +- api/nginx.conf | 20 + api/v1/migrations/0001_initial.py | 44 + api/v1/models/server.py | 71 +- api/v1/serializers/server.py | 18 +- api/v1/views/server.py | 28 +- client/index.html | 7 +- client/public/android-chrome-192x192.png | Bin 0 -> 12242 bytes client/public/android-chrome-512x512.png | Bin 0 -> 52469 bytes client/public/apple-touch-icon.png | Bin 0 -> 12087 bytes client/public/favicon-16x16.png | Bin 0 -> 430 bytes client/public/favicon-32x32.png | Bin 0 -> 807 bytes client/public/favicon.ico | Bin 0 -> 15406 bytes client/public/site.webmanifest | 1 + client/src/apiUtil.tsx | 4 +- client/src/components/Accordion.tsx | 2 +- client/src/components/Header.tsx | 1 - client/src/components/ServerCard.tsx | 223 +- client/src/data/ServerData.tsx | 1863 +++++++++++------ client/src/images/copy-image.png | Bin 0 -> 1364 bytes client/src/index.css | 1 + client/src/pages/Home.tsx | 75 +- 25 files changed, 1532 insertions(+), 851 deletions(-) rename {client/src/data => api}/defaultLsbSettings.json (100%) create mode 100644 api/nginx.conf create mode 100644 api/v1/migrations/0001_initial.py create mode 100644 client/public/android-chrome-192x192.png create mode 100644 client/public/android-chrome-512x512.png create mode 100644 client/public/apple-touch-icon.png create mode 100644 client/public/favicon-16x16.png create mode 100644 client/public/favicon-32x32.png create mode 100644 client/public/favicon.ico create mode 100644 client/public/site.webmanifest create mode 100644 client/src/images/copy-image.png diff --git a/.devcontainer/docker-compose.yml b/.devcontainer/docker-compose.yml index b570fa6..4314e84 100644 --- a/.devcontainer/docker-compose.yml +++ b/.devcontainer/docker-compose.yml @@ -17,6 +17,7 @@ services: volumes: - ixion-dev-node_modules:/workspace/client/node_modules - ../client:/workspace/client:cached + - ../api:/workspace/api:cached ports: - 3000:3000 depends_on: @@ -32,6 +33,7 @@ services: volumes: - ixion-dev-static:/workspace/api/static - ../api:/workspace/api:cached + - ../client:/workspace/client:cached ports: - 8000:8000 depends_on: diff --git a/api/api/urls.py b/api/api/urls.py index 8d6aec3..62f60ad 100644 --- a/api/api/urls.py +++ b/api/api/urls.py @@ -18,12 +18,13 @@ Including another URLconf from django.contrib import admin from django.urls import path, include from rest_framework import routers -from v1.views import server +from v1.views.server import ServerViewSet, ServerDetailsViewSet router = routers.DefaultRouter() -router.register(r"servers", server.ServerViewSet, "v1") +router.register(r"servers", ServerViewSet, basename="servers") +router.register(r"server", ServerDetailsViewSet, basename="server") urlpatterns = [ path("admin/", admin.site.urls), - path("v1/", include(router.urls)), + path("", include(router.urls)), ] diff --git a/client/src/data/defaultLsbSettings.json b/api/defaultLsbSettings.json similarity index 100% rename from client/src/data/defaultLsbSettings.json rename to api/defaultLsbSettings.json diff --git a/api/docker-compose.yml b/api/docker-compose.yml index ebc0731..0701ec4 100644 --- a/api/docker-compose.yml +++ b/api/docker-compose.yml @@ -5,8 +5,9 @@ services: restart: always volumes: - .:/app - ports: - - 8000:8000 + - static:/app/static + expose: + - 8000 depends_on: - redis celery: @@ -28,3 +29,14 @@ services: redis: image: redis:alpine restart: always + nginx: + image: nginx + ports: + - 9969:80 + volumes: + - ./nginx.conf:/etc/nginx/conf.d/default.conf + - ./static:/app/static + depends_on: + - api +volumes: + static: diff --git a/api/nginx.conf b/api/nginx.conf new file mode 100644 index 0000000..2fa7a37 --- /dev/null +++ b/api/nginx.conf @@ -0,0 +1,20 @@ +upstream ixion_api { + server api:8000; +} + +server { + + listen 80; + + location / { + proxy_pass http://ixion_api; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header Host $host; + proxy_redirect off; + } + + location /static/ { + alias /app/static/; + } + +} diff --git a/api/v1/migrations/0001_initial.py b/api/v1/migrations/0001_initial.py new file mode 100644 index 0000000..560fb00 --- /dev/null +++ b/api/v1/migrations/0001_initial.py @@ -0,0 +1,44 @@ +# Generated by Django 5.0.4 on 2024-04-15 16:13 + +import v1.models.server +from django.db import migrations, models + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [] + + operations = [ + migrations.CreateModel( + name="Server", + fields=[ + ( + "id", + models.BigAutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ("name", models.CharField(editable=False, max_length=255, null=True)), + ( + "url", + models.CharField( + max_length=255, + validators=[v1.models.server.OptionalSchemeURLValidator()], + ), + ), + ("max_level", models.IntegerField(editable=False, null=True)), + ("settings", models.JSONField(editable=False, null=True)), + ("customizations", models.JSONField(editable=False, null=True)), + ("login_limit", models.IntegerField(editable=False, null=True)), + ("active_sessions", models.IntegerField(editable=False, null=True)), + ("created", models.DateTimeField(auto_now_add=True)), + ("updated", models.DateTimeField(auto_now=True)), + ("inactivity_counter", models.IntegerField(editable=False, null=True)), + ], + ), + ] diff --git a/api/v1/models/server.py b/api/v1/models/server.py index 0ffbb6e..b05c0fe 100644 --- a/api/v1/models/server.py +++ b/api/v1/models/server.py @@ -1,4 +1,6 @@ +import json import os +import socket import requests from django.db import models from django.core.validators import URLValidator @@ -6,13 +8,23 @@ from django.core.exceptions import ValidationError from urllib.parse import urlparse from v1.common.logging import logger - +# Required and recommended settings used to build the initial client server card required_settings = [ "MAIN.SERVER_NAME", "MAIN.MAX_LEVEL", "LOGIN.LOGIN_LIMIT", ] +recommended_settings = [ + "MAIN.ENABLE_TRUST_CASTING", + "MAP.LEVEL_SYNC_ENABLE", + "LOGIN.RISE_OF_ZILART", + "LOGIN.CHAINS_OF_PROMATHIA", + "LOGIN.TREASURES_OF_AHT_URGHAN", + "LOGIN.WINGS_OF_THE_GODDESS", + "LOGIN.SEEKERS_OF_ADOULIN", +] + class OptionalSchemeURLValidator(URLValidator): def __call__(self, value): @@ -23,12 +35,16 @@ class OptionalSchemeURLValidator(URLValidator): class Server(models.Model): + name = models.CharField(max_length=255, null=True, editable=False) url = models.CharField( - max_length=400, + max_length=255, null=False, validators=[OptionalSchemeURLValidator()], ) + max_level = models.IntegerField(null=True, editable=False) settings = models.JSONField(null=True, editable=False) + customizations = models.JSONField(null=True, editable=False) + login_limit = models.IntegerField(null=True, editable=False) active_sessions = models.IntegerField(null=True, editable=False) created = models.DateTimeField(auto_now_add=True) updated = models.DateTimeField(auto_now=True) @@ -73,26 +89,59 @@ class Server(models.Model): def parse_server_api(self): try: # Request server settings from API - response = requests.get(f"http://{self.url}/api/settings") - response.raise_for_status() # Raise an exception for HTTP errors (4xx and 5xx) - json_data = response.json() - - # Validate the JSON structure - if not isinstance(json_data, dict): + response = requests.get(f"http://{self.url}/api/settings", timeout=5) + response.raise_for_status() + server_settings = response.json() + if not isinstance(server_settings, dict): return False + # Check for required settings for setting in required_settings: - if setting not in json_data: + if setting not in server_settings: return False + self.name = server_settings["MAIN.SERVER_NAME"] + self.max_level = server_settings["MAIN.MAX_LEVEL"] + self.login_limit = server_settings["LOGIN.LOGIN_LIMIT"] - self.settings = json_data + # Check customizations + with open("defaultLsbSettings.json", "r") as default_settings_file: + default_settings = json.load(default_settings_file) + customizations = {} + + customizations["LOGIN.CLIENT_VER"] = server_settings["LOGIN.CLIENT_VER"] + + for key, value in server_settings.items(): + if key not in required_settings and ( + key in recommended_settings + or key not in default_settings + or default_settings[key] != value + ): + customizations[key] = value + + self.customizations = customizations + self.settings = server_settings # Request the active session count from API response = requests.get(f"http://{self.url}/api/sessions") - response.raise_for_status() # Raise an exception for HTTP errors (4xx and 5xx) + response.raise_for_status() session_count = response.text if session_count.isdigit(): self.active_sessions = int(session_count) + + # Test other server ports (you can actually change all of these?) + # ports_to_check = [ + # 54230, # NETWORK.LOGIN_DATA_PORT, NETWORK.MAP_PORT + # 54231, # NETWORK.LOGIN_AUTH_PORT + # 54001, # NETWORK.LOGIN_VIEW_PORT + # ] + # for port in ports_to_check: + # try: + # with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s: + # s.settimeout(5) + # s.connect((self.url, port)) + # except socket.error: + # return False + return True except requests.RequestException: diff --git a/api/v1/serializers/server.py b/api/v1/serializers/server.py index f8dffd5..992db72 100644 --- a/api/v1/serializers/server.py +++ b/api/v1/serializers/server.py @@ -6,10 +6,26 @@ from django.core.exceptions import ValidationError class ServerSerializer(serializers.ModelSerializer): class Meta: model = Server - fields = "__all__" + fields = [ + "id", + "name", + "url", + "max_level", + "customizations", + "login_limit", + "active_sessions", + "updated", + "inactivity_counter", + ] def create(self, validated_data): try: return super().create(validated_data) except ValidationError as e: raise serializers.ValidationError({"url": e.messages}) + + +class ServerDetailsSerializer(serializers.ModelSerializer): + class Meta: + model = Server + fields = "__all__" diff --git a/api/v1/views/server.py b/api/v1/views/server.py index d4addfd..eaf303e 100644 --- a/api/v1/views/server.py +++ b/api/v1/views/server.py @@ -1,19 +1,9 @@ from rest_framework import viewsets, mixins from django.db.models.fields.json import KT -from v1.serializers.server import ServerSerializer +from v1.serializers.server import ServerDetailsSerializer, ServerSerializer from v1.models.server import Server -class ServerView( - mixins.CreateModelMixin, - mixins.RetrieveModelMixin, - mixins.ListModelMixin, - viewsets.GenericViewSet, -): - serializer_class = ServerSerializer - queryset = Server.objects.all() - - class ServerViewSet( mixins.CreateModelMixin, mixins.RetrieveModelMixin, @@ -28,14 +18,6 @@ class ServerViewSet( "annotate_field": "settings__MAIN.SERVER_NAME", "filter_type": "startswith", }, - "level_cap_min": { - "annotate_field": "settings__MAIN.MAX_LEVEL", - "filter_type": "gte", - }, - "level_cap_max": { - "annotate_field": "settings__MAIN.MAX_LEVEL", - "filter_type": "lte", - }, } def get_queryset(self): @@ -58,3 +40,11 @@ class ServerViewSet( queryset._result_cache = None return queryset + + +class ServerDetailsViewSet( + mixins.RetrieveModelMixin, + viewsets.GenericViewSet, +): + queryset = Server.objects.all() + serializer_class = ServerDetailsSerializer diff --git a/client/index.html b/client/index.html index a611cce..1385ce5 100644 --- a/client/index.html +++ b/client/index.html @@ -16,7 +16,12 @@ href="https://fonts.googleapis.com/icon?family=Material+Icons" /> - IXION - FFXI Private Server Directory + + + + + + IXION - Private Servers
diff --git a/client/public/android-chrome-192x192.png b/client/public/android-chrome-192x192.png new file mode 100644 index 0000000000000000000000000000000000000000..2df181e6f96939d8c917e81f3012c93feb88ea4e GIT binary patch literal 12242 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%0D$Bn2374Y{Uspmm|g12Xvv^!0{yiOXetVXiY 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

CVez z0Lut{L4N(H){cR4F3=aCv9QzmK)FZb{@78hGWt2|SMknjEx&K^XrQQEo(VWqZ6d{s zihL&=bRFk%|CxIao8O0~2sw{^8EB_t(tTOG-L^`xo><22$;H&wS)x_*^v^mgyZ^%Jyn z!i#ILLI=)bBVwN?6&qZCeex#IjLI_oHBcwtN`Cq!&Hld3?Gzl%c3ooGJKtQ%&|1P~ z2eCv0jE(%2DKql|U>WHqRAvi7qTb^bEAY)`I@`0v_e_|aFZv^AS=)4!M#)GRCV!yO zMw`-NLu*%jc`1EgQ*BG@_jToyrNYSTM+kP5Abm^*!N;brSW?wdA`!-5DajXR)%i{2H=@sbd}X+Y-t?gM_qh{Zk4hm z&rkXJ(w~{5#Sv61jA}UiczcI+SJL(rM{%u~*J?)-b%w zIP7{i$JBOfNmb691_#%jkA~_+UEq>bMRCI`;TqaR_MY^?5k;*M0W|wMN5n^VCv7(V zR{S3!+b9iysfKy~o2O$Stm*%;uK$I(qIuZ=GyL`c5?`CqaW{Ylh3ju_s>7$o`9rdD z9iv;Uxf@XDF){O_j;_*9TUj=UD^fhb!4rkA<`w@Tlu39U0y{orUH^dTU*61aPA%z& z$f{sNFCYxXVfLEBT++JX8J@Mnj(*T0MnO$a#X zx^S=g7W2r^h<8Yk0C?n3#MMG1a0$2)GbleVRQxEBex=5}*Y&2Ix#W()P3$d1mJ-du PqhIREI!e_FHevq*6tHOm literal 0 HcmV?d00001 diff --git a/client/public/favicon-16x16.png b/client/public/favicon-16x16.png new file mode 100644 index 0000000000000000000000000000000000000000..a3805090c29e9a6cd0766967c93daaf7000f660e GIT binary patch literal 430 zcmV;f0a5;mP)Px$Xh}ptR5(wilRZcSK@f%CY^*Gj5L+9ulW=!?XelP5wOD8+Bw`^J0c#6I3u_^T zScnN&X(85%iC8N4N3atcTSJn<%IwG;mtY|Az}sqIW2z!YvaD25cbuvxWq$%`IZb-#g_-vbGVrhK5`x>pU;+*Qmgnr-I;K)9+56KxW32j-7Z@kJ2Lgd)BI zcnD&mNd}gQ`2s*QKA^W>@4qAT0I@}ky~fx^8fIlCnhS(yevY*Auw(h{#K>bB0f;>3 YAEW~SibwVl;Q#;t07*qoM6N<$f&>w=RsaA1 literal 0 HcmV?d00001 diff --git a/client/public/favicon-32x32.png b/client/public/favicon-32x32.png new file mode 100644 index 0000000000000000000000000000000000000000..246408e2b354d762586ef2d2b5a5d604d62ebbf3 GIT binary patch literal 807 zcmV+?1K9kDP)Px%;Ymb6R9HvtS21W5K@@#|c25XFQNd10E5_Tq*-OAq5R2GYTB#8XDh5dqEfiFY zpoJm{5-}3NsFkISDMYXnlH0x8Gh!P|Cqz*~Ah$bTX1!ZCo6Q-H;5NBxZ|48m$NTU7 zKTBxySGBB8nC8bY-S-a_Ja4I;qo|!Eo#T~C`=Qk5FkgrRR=ND8owK$8tYN?_mGZ3A zkZBm>-Ltc9oL0A3+$o5T6L17X5`fj%_rUBj@Ln;$k(J7ic-~x3kMx6Jkc1es%H>*o zZw-K|l#@hs7R-wQbDykKX1siU7w2-<1#pgm9j$6jz)uA{rF#9YRjYk>i^X1pXc|Pj znfY14bq87k&}f4oc&8II2o~Z7DRm7*dmD+%06qYi2XMZzHUXduz+nI#;TrP-3vrK> zy3;(%FpQ%~R#Qb7M;$}E1m=f6@W^)EZ)q-Yq}*cymq2tGKxdR2V>8L#x-O`eN*9TF zyqQ42sl4O7YW{C!!HHT?>G1@hyktSO8fmHUG7vTGr z*2IPwsM_{@0W0x%y84+G?f{^IGkK zdE}t$-bx;@#<1!D-Y< z5DFL;fR2L#`UNnWWJ&LZ!X(m;>0svmg6qC$X+uxS=OF3_Fl{-`u|y+^JPlw4z_!Nz zpXYVd?%DQR0DWMdvRwDfDgb(_Q7*SHQY`ap0`dSRKs1`HnNq4(5KYEj-V9k$%mYG* znKf!>9@agBff^B=h~8DNekUOXlqu#xz94AETn05UH@lH&g^ z!X$vIl-b%a^G2i@$+Q)RgC^@?6ta-zIL`D!lKGV^^GoSQujNc+H-;9%A;au!%C$Uo l_@pdueVS#?z-E1({{~gw=D{|;$*ceX002ovPDHLkV1lEMbglpZ literal 0 HcmV?d00001 diff --git a/client/public/favicon.ico b/client/public/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..47b11972f52a158dac9f0ea197eb76d2c5653adb GIT binary patch literal 15406 zcmeI2zi$*r6vy`zlqf`sOpz)?a)Ba*V*BoF2O&j>hAMT^gpf#SqR_yfkaQ6xE{Nnr z2q7U?G)R8|t*<@uv7w(eHM< zz07x=GMM+^7O4;qvI~T zTjiN=?=<6%U-IiTWEq17kH)Uk=chhvbWS|-RdR)X)+7hsq_2DQm~nXE&F;(ESJpYZme&fcE{8|vd$rFt~k$*?Vuk(X{b!+C2ZKp5!v7X;5w#YNnYRA;b zPcQp7I~%{J*yOoQ>A!6Kqx8+sCkN%n=QR4G_+K{tb+xZ~%_t8pFD$HgLZ7wUccw%; zDW6}^x+ml?+v0>VXz+w`=t3vDLt1zbA3CM)k#sFWpP5Pa$==y+H+?*?>1>Brc;d6j zAd3!kS)K7RV`V>!)$u2f=I7%dB!ADdtGz6_rgq*Nqt6&Lm)q}UvYGAVQISuJu=tpr zK3&-xe(H4oP!$nHMezp>9(X;uoG**)6^()2*CF4Z@cD}DJ373+EV>-6%9XVXe7SBtJs!~DHrFRPS4bMp#%usoY|G ze=X$mF^K0`#>ew!`uH54P)>B7O=9RmNMrB9m#%qv?YtZFuguliL#NlCLGyWCUp!f^ zh!Z!))$XB5I`Cui+|vi%%)j}o&*S&2@cTTH)&5W{kXue$3`?F$v_?Th4J ztk3@C_7eJKyt_gBzuUL9Lw)bxyRoKq=p=C4YTv5u#yN@9mi+oHJ+H^L-*E=2Gs-*1 zqp0615tt~_{QRxGS-zxhRjtZ9{;j z6nz!j8HS^Ip3!&sJ;M%;f91oQ-R}AUC#mz7IgMY{JJmx=ABj7hk8~Q@tLSoiv0-gt z)0}NQ2aBUuy|R9iU(I8SZ!avwpJ?oq8n}H-tP|f(biSGNgyF$}9zS3V8a!&b^9y*_U-&Wu0We%%0eybvPd~O}_`wM;O-BA168)J=yIbBwsKPj9uit!IcU-5WW7=vbc z;5Bpc=YdXiJM6l~H;dxdAsl_}x3cy0ed6iod@H@EHf!V9)(GYKr{97X87rGRFY|P> z4#eyDbw?I68+qTP`0nQe2mX8~^g}uR0XMSf&^-KgU=ADD9B_`;lRo0zj~VBu!u73& z-^FJV#wWV5b!t4+RyxZw-m{wXm7cGBUP7u0{xHu7uW;vdKlJm`PUd3clpOiunc_TG z>ur2y@|&gQW3?QA;WLFU)&brA`SI1Q`$P4a*uBZ*Xx3CQnqDMk$q8AGc5$TZAXf~> z?_ws*X;#)g599-vU(<(RsE&BR$^I@qOKQf?Hs7VQmSdO?Z7dDLUmP>}wUdu#v)qbr zu)S1XYYZ^^J+0e*$+FKI-;>|NwH$_rbCGU$Z6%YH{Mw2g3-_k2&ki=QC0Ub`G?z87 z_}HrSvs%p6Gul`AMiS=YVe!BjQ0nF`{HE`p3u$Z+Z+SfZ7Pf?+eH!0A1xuc1^qu6W zBa!EO1vSBtZ@h)EgzvoP+w_e;tCgd61_glQ160?4j9qL_!U;29N z|2{qJ+xReNZyru7@AT*jErvIg_wTb>qvy$8^6B*L`!wsH9@lzNyIl60*4MA!sl`@& zdf2z|ZsNrEyP3a-Lo|5SM(Cn;%GH1OLwmYk2D^0MRV(vZh+$Ih!3_3B<8ONWkQ1By z{0{O^zNffqNdC;@c6UwRzv`?XwD!NMnVh)O`l0Fd8*$X+`WgJxf?CJMzP~VsCci&| zpP$yCvG4ETZ|?a?VQuf%ZxeieHT2zTL-FLF1x@7nVbjlVn|^+-eS2HqFWBc5SMGh^ ze@4Ro(@rP7Bi>#8e&N0!@eRkGzlUcJWG4Hg)}UU>YCh`L+P@z;Y&*H~e$D%;=3#tK zpRt|jszc@t{yMxqu-SC~^FGb_jN+en5bbZx`u_%J0(y7Q*-|tAKQoA=H@(;01OEa2 C=1v*_ literal 0 HcmV?d00001 diff --git a/client/public/site.webmanifest b/client/public/site.webmanifest new file mode 100644 index 0000000..45dc8a2 --- /dev/null +++ b/client/public/site.webmanifest @@ -0,0 +1 @@ +{"name":"","short_name":"","icons":[{"src":"/android-chrome-192x192.png","sizes":"192x192","type":"image/png"},{"src":"/android-chrome-512x512.png","sizes":"512x512","type":"image/png"}],"theme_color":"#ffffff","background_color":"#ffffff","display":"standalone"} \ No newline at end of file diff --git a/client/src/apiUtil.tsx b/client/src/apiUtil.tsx index 989accb..2b168e2 100644 --- a/client/src/apiUtil.tsx +++ b/client/src/apiUtil.tsx @@ -23,7 +23,7 @@ export const fetchDemo = async () => { export const fetchData = async (queryParams = {}) => { const queryString = new URLSearchParams(queryParams).toString(); - const url = `${import.meta.env.PROD ? 'https://api.ixion.dev' : 'http://localhost:8000'}/v1/servers/${queryString ? `?${queryString}` : ''}`; + const url = `${import.meta.env.PROD ? 'https://api.ixion.dev' : 'http://localhost:8000'}/servers/${queryString ? `?${queryString}` : ''}`; try { const response = await fetch(url, { signal: AbortSignal.timeout(5000), @@ -47,7 +47,7 @@ export const fetchData = async (queryParams = {}) => { export const postData = async (inputText: string) => { try { const response = await fetch( - `${import.meta.env.PROD ? 'https://api.ixion.dev' : 'http://localhost:8000'}/v1/servers/`, + `${import.meta.env.PROD ? 'https://api.ixion.dev' : 'http://localhost:8000'}/servers/`, { method: 'POST', headers: { diff --git a/client/src/components/Accordion.tsx b/client/src/components/Accordion.tsx index 5024e83..f1838fb 100644 --- a/client/src/components/Accordion.tsx +++ b/client/src/components/Accordion.tsx @@ -30,7 +30,7 @@ export const AccordionSummary = styled((props: AccordionSummaryProps) => ( '& .MuiAccordionSummary-content': { margin: theme.spacing(0), }, - boxShadow: '0 0 10px rgba(0, 0, 0, 0.1)', // Adjust the size and opacity as needed + boxShadow: '0 0 10px rgba(0, 0, 0, 0.1)', minHeight: '32px', })); diff --git a/client/src/components/Header.tsx b/client/src/components/Header.tsx index 1a01516..baf1410 100644 --- a/client/src/components/Header.tsx +++ b/client/src/components/Header.tsx @@ -75,7 +75,6 @@ export default function Header({ sx={{ color: 'inherit', textDecoration: 'none', - userSelect: 'none', flexGrow: 1, }} > diff --git a/client/src/components/ServerCard.tsx b/client/src/components/ServerCard.tsx index d377e7a..4e08755 100644 --- a/client/src/components/ServerCard.tsx +++ b/client/src/components/ServerCard.tsx @@ -1,4 +1,6 @@ import { + Check, + Close, ContentCopy, ExpandMore, Launch, @@ -10,13 +12,9 @@ import { Box, Card, CardContent, + Chip, Divider, IconButton, - Table, - TableBody, - TableCell, - TableContainer, - TableRow, ToggleButton, ToggleButtonGroup, Tooltip, @@ -25,7 +23,11 @@ import { } from '@mui/material'; import { useState } from 'react'; import { Link } from 'react-router-dom'; -import ServerData, { ServerSettingsInfo } from '../data/ServerData'; +import ServerData, { + ServerSetting, + ServerSettingsInfo, +} from '../data/ServerData'; +import CopyImageIcon from '../images/copy-image.png'; import { Accordion, AccordionDetails, AccordionSummary } from './Accordion'; export default function ServerCard({ server }: { server: ServerData }) { @@ -63,15 +65,75 @@ export default function ServerCard({ server }: { server: ServerData }) { }; function formatExternalUrl(url: string): string { - // Check if the URL starts with a valid protocol + // prepend 'https://' to the URL if it's not already there if (!/^https?:\/\//i.test(url)) { - // If not, prepend 'https://' to the URL return `https://${url}`; } - // Otherwise, return the original URL return url; } + const renderSettingsChip = ([key, value]: [ + string, + boolean | string | number, + ]) => { + if (!ServerSettingsInfo[key]) return null; + + const transformValue = ( + v: boolean | string | number, + setting: ServerSetting + ): string | number | boolean => { + return setting.transform?.(v) ?? v; + }; + + let chipValue: string | number | boolean | JSX.Element = transformValue( + value, + ServerSettingsInfo[key] + ); + if (typeof chipValue === 'boolean') { + chipValue = chipValue ? ( + + ) : ( + + ); + } + + return ( + + + ) + } + icon={typeof chipValue === 'object' ? chipValue : undefined} + size="small" + className="m-1 pr-1" + sx={{ + '& .MuiChip-avatar': { + width: 'auto', + marginX: 0, + order: 2, + }, + '& .MuiChip-icon': { + marginX: 0, + order: 2, + }, + '&> .MuiChip-label': { + paddingRight: 0.5, + }, + boxShadow: '0px 3px 3px rgba(0, 0, 0, .25)', + }} + /> + + ); + }; + const expansions = [ }> - {server.settings['LOGIN.MAINT_MODE'] === 1 ? ( + {server.customizations['LOGIN.MAINT_MODE'] === 1 ? ( alpha(theme.palette.text.primary, 0.87)} sx={{ lineHeight: 1.0 }} > - {server.settings['MAIN.SERVER_NAME']} + {server.name} - {typeof server.settings['API.WEBSITE'] === 'string' && - server.settings['API.WEBSITE'] !== '' && ( + {typeof server.customizations['API.WEBSITE'] === 'string' && + server.customizations['API.WEBSITE'] !== '' && ( @@ -241,95 +306,57 @@ export default function ServerCard({ server }: { server: ServerData }) { variant="h5" color={(theme) => theme.palette.text.secondary} > - {`Lv.${server.settings['MAIN.MAX_LEVEL']}`} + {`Lv.${server.max_level}`} - - - - - - - - {`${ServerSettingsInfo['LOGIN.LOGIN_LIMIT'].name}: `} - {server.settings['LOGIN.LOGIN_LIMIT'] === 0 - ? 'unlimited' - : server.settings['LOGIN.LOGIN_LIMIT']} - - - - - - - {`${ServerSettingsInfo['MAIN.ENABLE_TRUST_CASTING'].name}: `} - {server.settings['MAIN.ENABLE_TRUST_CASTING'] === 1 - ? 'Enabled' - : 'Disabled'} - - - - - - - {`${ServerSettingsInfo['MAP.LEVEL_SYNC_ENABLE'].name}: `} - {server.settings['MAP.LEVEL_SYNC_ENABLE'] - ? 'Enabled' - : 'Disabled'} - - - - - *': { borderBottom: 'unset' } }}> - - - - {`${ServerSettingsInfo['MAP.SPEED_MOD'].name}: `} - {typeof server.settings['MAP.SPEED_MOD'] === 'number' - ? (server.settings['MAP.SPEED_MOD'] < 0 ? '' : '+') + - ((50 + server.settings['MAP.SPEED_MOD']) / 50 - 1) * - 100 - : '???'} - % - - - - - -
-
+ {server.customizations['MAIN.SERVER_MESSAGE'] && ( + <> + + + {server.customizations['MAIN.SERVER_MESSAGE']} + + + + + )} + + {Object.entries(server.customizations).map(renderSettingsChip)} +
- + {server.active_sessions} active sessions + {server.login_limit !== 1 && ( + + event.preventDefault()} + /> + + )} - + Updated: {new Date(server.updated).toLocaleString()} diff --git a/client/src/data/ServerData.tsx b/client/src/data/ServerData.tsx index bec582a..7d3a14b 100644 --- a/client/src/data/ServerData.tsx +++ b/client/src/data/ServerData.tsx @@ -1,4 +1,11 @@ -import LsbDefaults from './defaultLsbSettings.json'; +import LsbDefaults from '../../../api/defaultLsbSettings.json'; + +export interface ServerSetting { + name: string; + description: string; + unit?: string; + transform?: (arg: boolean | string | number) => boolean | string | number; +} interface ServerSettings { [key: string]: boolean | string | number; @@ -6,8 +13,11 @@ interface ServerSettings { export default interface ServerData { id: number; + name: string; url: string; - settings: ServerSettings; + max_level: number; + customizations: ServerSettings; + login_limit: number; active_sessions: number; updated: string; inactivity_counter: number; @@ -15,1222 +25,1735 @@ export default interface ServerData { export const DemoServerData: ServerData = { id: 0, + name: 'LandSandBoat Demo', url: 'github.com/LandSandBoat/server', - settings: { + max_level: 99, + customizations: { ...LsbDefaults, - 'MAIN.SERVER_NAME': 'LandSandBoat Demo', 'API.WEBSITE': 'https://landsandboat.github.io/server/', }, + login_limit: 0, active_sessions: 0, updated: new Date().toISOString(), inactivity_counter: 0, }; -export const ServerSettingsInfo: Record< - string, - Record -> = { +export const ServerSettingsInfo: Record = { 'LOGIN.ACCOUNT_CREATION': { name: 'Account Creation', - description: 'Allows account creation via the loader.', - }, - 'LOGIN.A_CRYSTALLINE_PROPHECY': { - name: 'A Crystalline Prophecy', - description: 'Displays "A Crystalline Prophecy" expansion.', - }, - 'LOGIN.A_MOOGLE_KUPOD_ETAT': { - name: "A Moogle Kupo d'Etat", - description: 'Displays "A Moogle Kupo d\'Etat" expansion.', - }, - 'LOGIN.A_SHANTOTTO_ASCENSION': { - name: 'A Shantotto Ascension', - description: 'Displays "A Shantotto Ascension" expansion.', - }, - 'LOGIN.CHAINS_OF_PROMATHIA': { - name: 'Chains of Promathia', - description: 'Displays "Chains of Promathia" expansion.', + description: 'Account creation via the loader.', }, 'LOGIN.CHARACTER_CREATION': { name: 'Character Creation', - description: 'Allows character creation through the lobby.', + description: 'Character creation through the lobby.', }, 'LOGIN.CHARACTER_DELETION': { name: 'Character Deletion', - description: 'Allows character deletion through the lobby.', + description: 'Character deletion through the lobby.', }, 'LOGIN.CLIENT_VER': { name: 'Client Version', description: 'Expected client version.', }, - 'LOGIN.DISABLE_MOB_NPC_CHAR_NAMES': { - name: 'Disable Mob/NPC Character Names', - description: - 'Blocks character creation with names of NPCs and Mobs in the database', - }, - 'LOGIN.HEROES_OF_ABYSSEA': { - name: 'Heroes of Abyssea', - description: 'Displays "Heroes of Abyssea" expansion.', - }, - 'LOGIN.LOGIN_LIMIT': { - name: 'Login Limit', - description: 'Number of simultaneous game sessions per IP.', - }, 'LOGIN.LOG_USER_IP': { - name: 'Logs User IP', - description: 'Logs user IP address to database.', - }, - 'LOGIN.MAINT_MODE': { - name: '', - description: '', - }, - 'LOGIN.MOG_WARDROBE_3': { - name: '', - description: '', - }, - 'LOGIN.MOG_WARDROBE_4': { - name: '', - description: '', - }, - 'LOGIN.MOG_WARDROBE_5': { - name: '', - description: '', - }, - 'LOGIN.MOG_WARDROBE_6': { - name: '', - description: '', - }, - 'LOGIN.MOG_WARDROBE_7': { - name: '', - description: '', - }, - 'LOGIN.MOG_WARDROBE_8': { - name: '', - description: '', - }, - 'LOGIN.RISE_OF_ZILART': { - name: 'Rise of the Zilart', - description: 'Displays "Rise of the Zilart" expansion.', - }, - 'LOGIN.SCARS_OF_ABYSSEA': { - name: 'Scars of Abyssea', - description: 'Displays "Scars of Abyssea" expansion.', - }, - 'LOGIN.SECURE_TOKEN': { - name: '', - description: '', - }, - 'LOGIN.SEEKERS_OF_ADOULIN': { - name: 'Seekers of Adoulin', - description: 'Displays "Seekers of Adoulin" expansion.', - }, - 'LOGIN.TREASURES_OF_AHT_URGHAN': { - name: 'Treasures of Aht Urghan', - description: 'Displays "Treasures of Aht Urghan" expansion.', + name: 'Logs IP', + description: 'User IP address logging.', }, 'LOGIN.VER_LOCK': { - name: 'Version Lock', - description: '0 = disabled; 1 = strict; 2 = greater than or equal', - }, - 'LOGIN.VISIONS_OF_ABYSSEA': { - name: 'Visions of Abyssea', - description: 'Displays "Visions of Abyssea" expansion.', - }, - 'LOGIN.WINGS_OF_THE_GODDESS': { - name: 'Wings of the Goddess', - description: 'Displays "Wings of the Goddess" expansion.', + name: 'Version Locked', + description: 'Requires the exact client version specified.', + transform: (arg) => { + return !!arg; + }, }, 'MAIN.ABSORB_SPELL_AMOUNT': { - name: '', - description: '', + name: 'Absorb Spell Amount', + description: 'How much of a stat gets absorbed by DRK absorb spells.', }, 'MAIN.ABSORB_SPELL_TICK': { - name: '', - description: '', + name: 'Absorb Spell Tick', + description: 'Duration of 1 absorb spell tick.', + transform: (arg) => { + return `${arg}s`; + }, }, 'MAIN.ABYSSEA_BONUSLIGHT_AMOUNT': { - name: '', - description: '', + name: 'Abyssea Bonus Light', + description: + 'Bonus added to player lights upon entering Abyssea, mainly used during events.', + transform: (arg) => { + return `+${arg}`; + }, }, 'MAIN.ABYSSEA_LIGHTS_DROP_RATE': { - name: '', - description: '', + name: 'Abyssea Lights Drop Rate', + description: 'Rate that mobs drop Abyssea light.', + transform: (arg) => { + return `${arg}%`; + }, }, 'MAIN.ACTIVATE_LAMP_TIME': { - name: '', - description: '', + name: 'Nyzul Lamps', + description: 'Time that lamps in Nyzul stay lit.', + transform: (arg) => { + const numValue = Number(arg); + if (!Number.isNaN(numValue)) { + return `${numValue / 1000}s`; + } + return '???'; + }, }, 'MAIN.ADVANCED_JOB_LEVEL': { - name: '', - description: '', + name: 'Advanced Jobs', + description: 'Minimum level to accept advanced job quests.', + transform: (arg) => { + return `Lv.${arg}`; + }, }, 'MAIN.AF1_QUEST_LEVEL': { - name: '', - description: '', + name: 'AF1 Quest', + description: 'Minimum level to start AF1 quest.', + transform: (arg) => { + return `Lv.${arg}`; + }, }, 'MAIN.AF2_QUEST_LEVEL': { - name: '', - description: '', + name: 'AF2 Quest', + description: 'Minimum level to start AF2 quest.', + transform: (arg) => { + return `Lv.${arg}`; + }, }, 'MAIN.AF3_QUEST_LEVEL': { - name: '', - description: '', + name: 'AF3 Quest', + description: 'Minimum level to start AF3 quest.', + transform: (arg) => { + return `Lv.${arg}`; + }, }, 'MAIN.ALLOW_MULTIPLE_EXP_RINGS': { - name: '', - description: '', + name: 'Multiple EXP Rings', + description: + 'Removes ownership restrictions on the Chariot, Empress, Emperor Band trio.', + transform: (arg) => { + return !!arg; + }, }, 'MAIN.ALL_MAPS': { - name: '', - description: '', + name: 'All Maps', + description: 'New characters receive all maps.', + transform: (arg) => { + return !!arg; + }, }, 'MAIN.AQUAVEIL_COUNTER': { - name: '', - description: '', + name: 'Aquaveil Counter', + description: + 'Base amount of hits Aquaveil absorbs to prevent spell interrupts.', }, 'MAIN.ASSAULT_MINIMUM': { - name: '', - description: '', + name: 'Assault Minimum', + description: + 'Minimum amount of players required to start an assault mission.', }, 'MAIN.BAYLD_RATE': { - name: '', + name: 'Quest Bayld Rate', description: '', + transform: (arg) => { + const numValue = Number(arg); + if (!Number.isNaN(numValue)) { + return `${numValue < 1 ? '' : '+'}${((numValue - 1) * 100).toFixed()}%`; + } + return '???'; + }, }, 'MAIN.BETWEEN_2DYNA_WAIT_TIME': { - name: '', - description: '', + name: 'Dynamis Lockout', + description: 'Time before Dynamis re-entry.', + transform: (arg) => { + const numValue = Number(arg); + if (!Number.isNaN(numValue)) { + return `${numValue}hr`; + } + return '???'; + }, }, 'MAIN.BIO_OVERWRITE': { - name: '', - description: '', + name: 'Dia Overwrites Bio', + description: 'Dia overwrites same tier Bio.', + transform: (arg) => { + return !!arg; + }, }, 'MAIN.BLINK_SHADOWS': { - name: '', - description: '', + name: 'Blink Shadows', + description: 'Number of shadows supplied by Blink spell.', }, 'MAIN.BLUE_POWER': { - name: '', - description: '', + name: 'Blue Magic Power', + description: 'Multiplies damage dealt by Blue Magic.', + transform: (arg) => { + const numValue = Number(arg); + if (!Number.isNaN(numValue)) { + return `${numValue < 1 ? '' : '+'}${((numValue - 1) * 100).toFixed()}%`; + } + return '???'; + }, }, 'MAIN.BOOK_EXP_RATE': { - name: '', + name: 'FoV/GoV EXP Rate', description: '', + transform: (arg) => { + const numValue = Number(arg); + if (!Number.isNaN(numValue)) { + return `${numValue < 1 ? '' : '+'}${((numValue - 1) * 100).toFixed()}%`; + } + return '???'; + }, }, 'MAIN.BYPASS_EXP_RING_ONE_PER_WEEK': { - name: '', - description: '', + name: 'Multiple EXP Rings Per Week', + description: 'Bypass the limit of one ring per Conquest Tally week.', + transform: (arg) => { + return !!arg; + }, }, 'MAIN.CAPACITY_RATE': { - name: '', + name: 'Quest Capacity Points Rate', description: '', + transform: (arg) => { + const numValue = Number(arg); + if (!Number.isNaN(numValue)) { + return `${numValue < 1 ? '' : '+'}${((numValue - 1) * 100).toFixed()}%`; + } + return '???'; + }, }, 'MAIN.CAP_CURRENCY_ACCOLADES': { - name: '', - description: '', + name: 'Unity Accolades Cap', + description: 'Unity Accolades currency cap.', }, 'MAIN.CAP_CURRENCY_BALLISTA': { - name: '', - description: '', + name: 'Ballista Points Cap', + description: 'Ballista Points currency cap.', }, 'MAIN.CAP_CURRENCY_SPARKS': { - name: '', - description: '', + name: 'Sparks Cap', + description: 'Sparks currency cap.', }, 'MAIN.CAP_CURRENCY_VALOR': { - name: '', - description: '', + name: 'Valor Cap', + description: 'Valor currency cap.', }, 'MAIN.CASKET_DROP_RATE': { - name: '', + name: 'Treasure Casket Drop Rate', description: '', + transform: (arg) => { + const numValue = Number(arg); + if (!Number.isNaN(numValue)) { + return `${numValue * 100}%`; + } + return '???'; + }, }, 'MAIN.CHEST_MAX_ILLUSION_TIME': { - name: '', - description: '', + name: 'Chest Max Illusion', + description: + 'Together with min time, determines the random range in which loot is unavailable from treasure chests.', + transform: (arg) => { + const numValue = Number(arg); + if (!Number.isNaN(numValue)) { + return `${numValue / 60} min`; + } + return '???'; + }, }, 'MAIN.CHEST_MIN_ILLUSION_TIME': { - name: '', - description: '', - }, - 'MAIN.CHOCOBO_RAISING_DISABLE_RETIREMENT': { - name: '', - description: '', - }, - 'MAIN.CHOCOBO_RAISING_GIL_MULTIPLIER': { - name: '', - description: '', - }, - 'MAIN.CHOCOBO_RAISING_STAT_GROWTH_CAP': { - name: '', - description: '', - }, - 'MAIN.CHOCOBO_RAISING_STAT_NEG_MULTIPLIER': { - name: '', - description: '', - }, - 'MAIN.CHOCOBO_RAISING_STAT_POS_MULTIPLIER': { - name: '', - description: '', + name: 'Chest Min Illusion', + description: + 'Together with max time, determines the random range in which loot is unavailable from treasure chests.', + transform: (arg) => { + const numValue = Number(arg); + if (!Number.isNaN(numValue)) { + return `${numValue / 60} min`; + } + return '???'; + }, }, 'MAIN.COFFER_MAX_ILLUSION_TIME': { - name: '', - description: '', + name: 'Coffer Max Illusion', + description: + 'Together with min time, determines the random range in which loot is unavailable from treasure coffers.', + transform: (arg) => { + const numValue = Number(arg); + if (!Number.isNaN(numValue)) { + return `${numValue / 60} min`; + } + return '???'; + }, }, 'MAIN.COFFER_MIN_ILLUSION_TIME': { - name: '', - description: '', + name: 'Coffer Max Illusion', + description: + 'Together with min time, determines the random range in which loot is unavailable from treasure coffers.', + transform: (arg) => { + const numValue = Number(arg); + if (!Number.isNaN(numValue)) { + return `${numValue / 60} min`; + } + return '???'; + }, }, 'MAIN.COSMO_CLEANSE_BASE_COST': { - name: '', - description: '', + name: 'Cosmo Cleanse Cost', + description: 'Base gil cost for a Cosmo Cleanse from Sagheera.', }, 'MAIN.CURE_POWER': { - name: '', - description: '', + name: 'Cure Power', + description: + 'Multiplies amount healed from Healing Magic, including the relevant Blue Magic.', + transform: (arg) => { + const numValue = Number(arg); + if (!Number.isNaN(numValue)) { + return `${numValue < 1 ? '' : '+'}${((numValue - 1) * 100).toFixed()}%`; + } + return '???'; + }, }, 'MAIN.CURRENCY_EXCHANGE_RATE': { - name: '', - description: '', + name: 'Dynamis Exchange Rate', + description: 'Currency exchange rate for small to large Dynamis currency.', + transform: (arg) => { + const numValue = Number(arg); + if (!Number.isNaN(numValue)) { + return `${numValue}:1`; + } + return '???'; + }, }, 'MAIN.DAILY_TALLY_AMOUNT': { - name: '', - description: '', + name: 'Daily Tally', + description: 'Amount of Daily Tally points granted per day.', }, 'MAIN.DAILY_TALLY_LIMIT': { - name: '', - description: '', + name: 'Daily Tally Cap', + description: 'Daily Tally currency cap.', }, 'MAIN.DARK_POWER': { - name: '', - description: '', - }, - 'MAIN.DEBUG_CHOCOBO_RAISING': { - name: '', - description: '', + name: 'Dark Magic Power', + description: 'Multiplies amount drained by Dark Magic.', + transform: (arg) => { + const numValue = Number(arg); + if (!Number.isNaN(numValue)) { + return `${numValue < 1 ? '' : '+'}${((numValue - 1) * 100).toFixed()}%`; + } + return '???'; + }, }, 'MAIN.DIA_OVERWRITE': { - name: '', - description: '', + name: 'Bio Overwrites Dia', + description: 'Bio overwrites same tier Dia.', + transform: (arg) => { + return !!arg; + }, }, 'MAIN.DIGGING_RATE': { - name: '', - description: '', - }, - 'MAIN.DIG_ABUNDANCE_BONUS': { - name: '', - description: '', + name: 'Dig Rate', + description: + 'Chance to receive an item from chocobo digging during favorable weather.', + transform: (arg) => { + return `${arg}%`; + }, }, 'MAIN.DIG_FATIGUE': { - name: '', - description: '', + name: 'Dig Fatigue', + description: 'Fatigue system for chocobo digging.', + transform: (arg) => { + return !!arg; + }, }, 'MAIN.DIG_GRANT_BORE': { - name: '', - description: '', + name: 'Dig Grant Bore', + description: 'Grants Bore dig ability.', + transform: (arg) => { + return !!arg; + }, }, 'MAIN.DIG_GRANT_BURROW': { - name: '', - description: '', - }, - 'MAIN.DISABLE_INACTIVITY_WATCHDOG': { - name: '', - description: '', + name: 'Dig Grant Burrow', + description: 'Grants Burrow dig ability.', + transform: (arg) => { + return !!arg; + }, }, 'MAIN.DISABLE_PARTY_EXP_PENALTY': { - name: '', - description: '', + name: 'Party EXP Penalty', + description: 'EXP is not penalized by party size.', + transform: (arg) => { + return !arg; + }, }, 'MAIN.DIVINE_POWER': { - name: '', - description: '', + name: 'Divine Magic Power', + description: 'Multiplies damage dealt by Divine Magic.', + transform: (arg) => { + const numValue = Number(arg); + if (!Number.isNaN(numValue)) { + return `${numValue < 1 ? '' : '+'}${((numValue - 1) * 100).toFixed()}%`; + } + return '???'; + }, }, 'MAIN.DYNA_LEVEL_MIN': { - name: '', - description: '', + name: 'Dynamis Entry', + description: 'Minimum level for entering Dynamis.', + transform: (arg) => { + return `Lv.${arg}`; + }, }, 'MAIN.DYNA_MIDNIGHT_RESET': { - name: '', - description: '', + name: 'Dynamis Midnight Reset', + description: + 'Dynamis lockout resets at midnight instead of hours since last entry.', }, 'MAIN.ELEMENTAL_DEBUFF_DURATION': { - name: '', - description: '', + name: 'Elemental Debuff Duration', + description: 'Base duration of elemental debuffs.', + transform: (arg) => { + return `${arg}s`; + }, }, 'MAIN.ELEMENTAL_POWER': { - name: '', - description: '', - }, - 'MAIN.ENABLE_ABYSSEA': { - name: '', - description: '', - }, - 'MAIN.ENABLE_ACP': { - name: '', - description: '', - }, - 'MAIN.ENABLE_AMK': { - name: '', - description: '', - }, - 'MAIN.ENABLE_ASA': { - name: '', - description: '', + name: 'Elemental Magic Power', + description: + 'Multiplies damage dealt by Elemental and non-drain Dark Magic.', + transform: (arg) => { + const numValue = Number(arg); + if (!Number.isNaN(numValue)) { + return `${numValue < 1 ? '' : '+'}${((numValue - 1) * 100).toFixed()}%`; + } + return '???'; + }, }, 'MAIN.ENABLE_CHOCOBO_RAISING': { - name: '', - description: '', - }, - 'MAIN.ENABLE_COP': { - name: '', + name: 'Chocobo Raising', description: '', }, 'MAIN.ENABLE_COP_ZONE_CAP': { - name: '', - description: '', + name: 'Capped CoP Zones', + description: 'CoP zones use their original level cap.', + transform: (arg) => { + return !!arg; + }, }, 'MAIN.ENABLE_DAILY_TALLY': { - name: '', - description: '', + name: 'Daily Tally', + description: 'Allows acquisition of daily points for gobbie mystery box.', + transform: (arg) => { + return !!arg; + }, }, 'MAIN.ENABLE_EXCHANGE_100S_TO_1S': { - name: '', - description: '', + name: 'Can Downgrade Dynamis Currency', + description: + 'Allows exchange of 100s to 1s, like you can with 10Ks to 100s.', }, 'MAIN.ENABLE_EXCHANGE_LIMIT': { - name: '', - description: '', + name: 'Sparks Spend Limit', + description: + 'Limits amount of sparks and Unity accolades that can be spent per week.', + transform: (arg) => { + return !!arg; + }, }, 'MAIN.ENABLE_FIELD_MANUALS': { - name: '', + name: 'Fields of Valor', description: '', + transform: (arg) => { + return !!arg; + }, }, 'MAIN.ENABLE_GARRISON': { - name: '', + name: 'Garrison', description: '', }, 'MAIN.ENABLE_GROUNDS_TOMES': { - name: '', + name: 'Grounds of Valor', description: '', + transform: (arg) => { + return !!arg; + }, }, 'MAIN.ENABLE_IMMUNOBREAK': { - name: '', + name: 'Immunobreak', description: '', }, 'MAIN.ENABLE_LOGIN_CAMPAIGN': { - name: '', + name: 'Login Campaign', description: '', + transform: (arg) => { + return !!arg; + }, }, 'MAIN.ENABLE_MAGIAN_TRIALS': { - name: '', + name: 'Magian Trials', description: '', + transform: (arg) => { + return !!arg; + }, }, 'MAIN.ENABLE_MONSTROSITY': { - name: '', + name: 'Monstrosity', description: '', + transform: (arg) => { + return !!arg; + }, }, 'MAIN.ENABLE_NYZUL_CASKETS': { - name: '', - description: '', + name: 'Nyzul Caskets', + description: 'Treasure caskets drop from NMs.', }, 'MAIN.ENABLE_ROE': { - name: '', + name: 'Records of Eminence', description: '', + transform: (arg) => { + return !!arg; + }, }, 'MAIN.ENABLE_ROE_TIMED': { - name: '', - description: '', - }, - 'MAIN.ENABLE_ROV': { - name: '', - description: '', - }, - 'MAIN.ENABLE_SOA': { - name: '', - description: '', + name: 'RoE Timed Objectives', + description: '4-hour timed records.', + transform: (arg) => { + return !!arg; + }, }, 'MAIN.ENABLE_SURVIVAL_GUIDE': { - name: '', - description: '', - }, - 'MAIN.ENABLE_TOAU': { - name: '', + name: 'Survival Guides', description: '', + transform: (arg) => { + return !!arg; + }, }, 'MAIN.ENABLE_TRUST_ALTER_EGO_EXPO': { - name: '', - description: '', - }, - 'MAIN.ENABLE_TRUST_ALTER_EGO_EXPO_ANNOUNCE': { - name: '', - description: '', + name: 'Alter Ego Expo', + description: 'HP%/MP%/Status Resistance', + transform: (arg) => { + return !!arg; + }, }, 'MAIN.ENABLE_TRUST_ALTER_EGO_EXTRAVAGANZA': { - name: '', - description: '', - }, - 'MAIN.ENABLE_TRUST_ALTER_EGO_EXTRAVAGANZA_ANNOUNCE': { - name: '', - description: '', + name: 'Alter Ego Extravaganza', + description: 'Certain extra trusts are available.', + transform: (arg) => { + return !!arg; + }, }, 'MAIN.ENABLE_TRUST_CASTING': { name: 'Trusts', description: '', - }, - 'MAIN.ENABLE_TRUST_CUSTOM_ENGAGEMENT': { - name: '', - description: '', + transform: (arg) => { + return !!arg; + }, }, 'MAIN.ENABLE_TRUST_QUESTS': { - name: '', - description: '', - }, - 'MAIN.ENABLE_TVR': { - name: '', - description: '', + name: 'Trust Quests', + description: 'Trust unlock quests.', + transform: (arg) => { + return !!arg; + }, }, 'MAIN.ENABLE_VIGIL_DROPS': { - name: '', - description: '', + name: 'Nyzul Vigil Weapon', + description: 'Vigil weapons drops from NMs.', }, 'MAIN.ENABLE_VOIDWALKER': { - name: '', - description: '', - }, - 'MAIN.ENABLE_VOIDWATCH': { - name: '', - description: '', - }, - 'MAIN.ENABLE_WOTG': { - name: '', - description: '', + name: 'Voidwalker', + description: 'Voidwalker NMs are enabled.', + transform: (arg) => { + return !!arg; + }, }, 'MAIN.ENM_COOLDOWN': { - name: '', - description: '', + name: 'ENM Cooldown', + description: 'Time before a player can obtain same KI for ENMs.', + transform: (arg) => { + const numValue = Number(arg); + if (!Number.isNaN(numValue)) { + return `${numValue}hr`; + } + return '???'; + }, }, 'MAIN.EQUIP_FROM_OTHER_CONTAINERS': { - name: '', - description: '', + name: 'Equip From Bags', + description: + 'Allows equipping items from Mog Satchel, Sack, and Case. Only possible with the use of client addons.', }, 'MAIN.EXCAVATION_BREAK_CHANCE': { - name: '', - description: '', + name: 'Excavation Break Chance', + description: 'Chance for the pickaxe to break during excavation.', + transform: (arg) => { + return `${arg}%`; + }, }, 'MAIN.EXCAVATION_RATE': { - name: '', - description: '', + name: 'Excavation Rate', + description: 'Chance to receive an item from excavation.', + transform: (arg) => { + return `${arg}%`; + }, }, 'MAIN.EXPLORER_MOOGLE_LV': { - name: '', - description: '', + name: 'Explorer Moogle', + description: 'Explorer Moogle teleportation.', + transform: (arg) => { + if (arg === 0) { + return 'disabled'; + } + return `Lv.${arg}`; + }, }, 'MAIN.EXP_RATE': { - name: '', + name: 'Quest EXP Rate', description: '', + transform: (arg) => { + const numValue = Number(arg); + if (!Number.isNaN(numValue)) { + return `${numValue < 1 ? '' : '+'}${((numValue - 1) * 100).toFixed()}%`; + } + return '???'; + }, }, 'MAIN.FORCE_SPAWN_QM_RESET_TIME': { - name: '', - description: '', + name: '??? Respawn', + description: 'Time ??? remains hidden after the mob it spawns despawns.', + transform: (arg) => { + const numValue = Number(arg); + if (!Number.isNaN(numValue)) { + return `${numValue / 60} min`; + } + return '???'; + }, }, 'MAIN.FOV_REWARD_ALLIANCE': { - name: '', - description: '', + name: 'FoV Alliance', + description: + 'Allows Fields of Valor rewards while being a member of an alliance.', + transform: (arg) => { + return !!arg; + }, }, 'MAIN.FREE_COP_DYNAMIS': { - name: '', - description: '', - }, - 'MAIN.FRIGICITE_TIME': { - name: '', - description: '', + name: 'Unlocked CoP Dynamis', + description: 'Allows entry to CoP Dynamis without mission completion.', + transform: (arg) => { + return !!arg; + }, }, 'MAIN.GARRISON_LOCKOUT': { - name: '', - description: '', + name: 'Garrison Lockout', + description: 'Time before a new garrison can be started.', + transform: (arg) => { + const numValue = Number(arg); + if (!Number.isNaN(numValue)) { + return `${numValue / 60} min`; + } + return '???'; + }, }, 'MAIN.GARRISON_NATION_BYPASS': { - name: '', - description: '', + name: 'Garrison Nation Bypass', + description: 'Bypasses garrison nation requirements.', }, 'MAIN.GARRISON_ONCE_PER_WEEK': { - name: '', - description: '', + name: 'Multiple Garrisons Per Week', + description: 'Allows more than one garrison per Conquest tally week.', + transform: (arg) => { + return !arg; + }, }, 'MAIN.GARRISON_PARTY_LIMIT': { - name: '', - description: '', + name: 'Garrison Party Limit', + description: 'Max party members for garrison.', }, 'MAIN.GARRISON_RANK': { - name: '', - description: '', + name: 'Garrison Nation Rank', + description: 'Minimum nation rank to start garrison.', }, 'MAIN.GARRISON_TIME_LIMIT': { - name: '', - description: '', + name: 'Garrison Time Limit', + description: 'Time before lose ongoing garrison.', + transform: (arg) => { + const numValue = Number(arg); + if (!Number.isNaN(numValue)) { + return `${numValue / 60} min`; + } + return '???'; + }, }, 'MAIN.GIL_RATE': { - name: '', + name: 'Quest Gil Rate', description: '', + transform: (arg) => { + const numValue = Number(arg); + if (!Number.isNaN(numValue)) { + return `${numValue < 1 ? '' : '+'}${((numValue - 1) * 100).toFixed()}%`; + } + return '???'; + }, }, 'MAIN.GOBBIE_BOX_MIN_AGE': { - name: '', - description: '', + name: 'Mystery Box Age', + description: + 'Minimum age before a character can sign up for Gobbie Mystery Box.', + transform: (arg) => { + return `${arg} days`; + }, }, 'MAIN.GOV_REWARD_ALLIANCE': { - name: '', - description: '', - }, - 'MAIN.HALLOWEEN_2005': { - name: '', - description: '', - }, - 'MAIN.HALLOWEEN_YEAR_ROUND': { - name: '', - description: '', + name: 'GoV Alliance', + description: + 'Allows Grounds of Valor rewards while being a member of an alliance.', + transform: (arg) => { + return !!arg; + }, }, 'MAIN.HARVESTING_BREAK_CHANCE': { - name: '', - description: '', + name: 'Harvesting Break Chance', + description: 'Chance for the sickle to break during harvesting.', + transform: (arg) => { + return `${arg}%`; + }, }, 'MAIN.HARVESTING_RATE': { - name: '', - description: '', + name: 'Harvesting Rate', + description: 'Chance to receive an item from harvesting.', + transform: (arg) => { + return `${arg}%`; + }, }, 'MAIN.HEALING_TP_CHANGE': { - name: '', - description: '', + name: 'Healing TP Change', + description: 'Change in TP for each healing tick. (/heal)', }, 'MAIN.HOMEPOINT_TELEPORT': { - name: '', - description: '', - }, - 'MAIN.INACTIVITY_WATCHDOG_PERIOD': { - name: '', + name: 'Home Point Teleport', description: '', + transform: (arg) => { + return !!arg; + }, }, 'MAIN.INITIAL_LEVEL_CAP': { - name: '', + name: 'Initial Level Cap', description: '', + transform: (arg) => { + return `Lv.${arg}`; + }, }, 'MAIN.ITEM_POWER': { - name: '', - description: '', + name: 'Item Power', + description: 'Effect of items such as Potions and Ethers.', + transform: (arg) => { + const numValue = Number(arg); + if (!Number.isNaN(numValue)) { + return `${numValue < 1 ? '' : '+'}${((numValue - 1) * 100).toFixed()}%`; + } + return '???'; + }, }, 'MAIN.LANTERNS_STAY_LIT': { - name: '', - description: '', + name: 'Den of Rancor Lanterns', + description: 'Time that lanterns in the Den of Rancor stay lit.', + transform: (arg) => { + const numValue = Number(arg); + if (!Number.isNaN(numValue)) { + return `${numValue / 60} min`; + } + return '???'; + }, }, 'MAIN.LOGGING_BREAK_CHANCE': { - name: '', - description: '', + name: 'Logging Break Chance', + description: 'Chance for the hatchet to break during logging.', + transform: (arg) => { + return `${arg}%`; + }, }, 'MAIN.LOGGING_RATE': { - name: '', - description: '', - }, - 'MAIN.MAX_LEVEL': { - name: '', - description: '', + name: 'Logging Rate', + description: 'Chance to receive an item from logging.', + transform: (arg) => { + return `${arg}%`; + }, }, 'MAIN.MINING_BREAK_CHANCE': { - name: '', - description: '', + name: 'Mining Break Chance', + description: 'Chance for the pickaxe to break during mining.', + transform: (arg) => { + return `${arg}%`; + }, }, 'MAIN.MINING_RATE': { - name: '', - description: '', - }, - 'MAIN.MONSTROSITY_DONT_WIPE_BUFFS': { - name: '', - description: '', - }, - 'MAIN.MONSTROSITY_INFAMY_MESSAGING': { - name: '', - description: '', - }, - 'MAIN.MONSTROSITY_INFAMY_RATIO': { - name: '', - description: '', - }, - 'MAIN.MONSTROSITY_PVP_MODE': { - name: '', - description: '', - }, - 'MAIN.MONSTROSITY_PVP_ZONE_BYPASS': { - name: '', - description: '', - }, - 'MAIN.MONSTROSITY_TELEPORT_TO_FERETORY': { - name: '', - description: '', - }, - 'MAIN.MONSTROSITY_TRIGGER_NPCS': { - name: '', - description: '', + name: 'Mining Rate', + description: 'Chance to receive an item from mining.', + transform: (arg) => { + return `${arg}%`; + }, }, 'MAIN.NEW_CHARACTER_CUTSCENE': { - name: '', + name: 'New Character Cutscene', description: '', + transform: (arg) => { + return !!arg; + }, }, 'MAIN.NINJUTSU_POWER': { - name: '', + name: 'Ninjutsu Power', description: '', + transform: (arg) => { + const numValue = Number(arg); + if (!Number.isNaN(numValue)) { + return `${numValue < 1 ? '' : '+'}${((numValue - 1) * 100).toFixed()}%`; + } + return '???'; + }, }, 'MAIN.NM_LOTTERY_CHANCE': { - name: '', + name: 'NM Lottery Chance', description: '', + transform: (arg) => { + const numValue = Number(arg); + if (!Number.isNaN(numValue)) { + if (numValue < 0) { + return '100%'; + } + return `${numValue < 1 ? '' : '+'}${((numValue - 1) * 100).toFixed()}%`; + } + return '???'; + }, }, 'MAIN.NM_LOTTERY_COOLDOWN': { - name: '', + name: 'NM Lottery Cooldown', description: '', + transform: (arg) => { + const numValue = Number(arg); + if (!Number.isNaN(numValue)) { + if (numValue < 0) { + return '0s'; + } + return `${numValue < 1 ? '' : '+'}${((numValue - 1) * 100).toFixed()}%`; + } + return '???'; + }, }, 'MAIN.NORMAL_MOB_MAX_LEVEL_RANGE_MAX': { - name: '', - description: '', + name: 'Mob Max Level Range Max', + description: 'Upper bound of max level range for normal mobs.', + transform: (arg) => { + return arg === 0 ? 'none' : arg; + }, }, 'MAIN.NORMAL_MOB_MAX_LEVEL_RANGE_MIN': { - name: '', - description: '', + name: 'Mob Max Level Range Min', + description: 'Lower bound of max level range for normal mobs.', + transform: (arg) => { + return arg === 0 ? 'none' : arg; + }, }, 'MAIN.NUMBER_OF_DM_EARRINGS': { - name: '', - description: '', + name: 'Divine Might Earrings', + description: + 'Number of earrings players can simultaneously own from Divine Might.', }, 'MAIN.OLDSCHOOL_G1': { - name: '', - description: '', + name: 'Old School Limit Break 1', + description: + 'Requires farming Exoray Mold, Bomb Coal, and Ancient Papyrus drops instead of allowing key item method.', }, 'MAIN.OLDSCHOOL_G2': { - name: '', - description: '', + name: 'Old School Limit Break 2', + description: + 'Requires the NMs for "Atop the Highest Mountains" be dead to get KI.', }, 'MAIN.PRISMATIC_HOURGLASS_COST': { - name: '', - description: '', + name: 'Prismatic Hourglass Cost', + description: 'Cost of the prismatic hourglass for Dynamis.', }, 'MAIN.REGIME_REWARD_THRESHOLD': { - name: '', - description: '', + name: 'Regime Reward Threshold', + description: + 'Max levels below FoV/GoV minimum suggested range player can earn EXP.', }, 'MAIN.REGIME_WAIT': { - name: '', - description: '', + name: 'Regime Cooldown', + description: 'Enables FoV/GoV game day cooldown.', + transform: (arg) => { + return !!arg; + }, }, 'MAIN.RELIC_2ND_UPGRADE_WAIT_TIME': { - name: '', - description: '', + name: 'Relic 2nd Stage Wait', + description: 'Wait time for 2nd relic upgrade.', + transform: (arg) => { + const numValue = Number(arg); + if (!Number.isNaN(numValue)) { + return `${numValue / 60 / 60}hr`; + } + return '???'; + }, }, 'MAIN.RELIC_3RD_UPGRADE_WAIT_TIME': { - name: '', - description: '', - }, - 'MAIN.RESTRICT_CONTENT': { - name: '', - description: '', + name: 'Relic 3rd Stage Wait', + description: 'Wait time for 3rd relic upgrade.', + transform: (arg) => { + const numValue = Number(arg); + if (!Number.isNaN(numValue)) { + return `${numValue / 60 / 60}hr`; + } + return '???'; + }, }, 'MAIN.RIVERNE_PORTERS': { - name: '', - description: '', + name: 'Riverne Teleports', + description: + 'Time that Unstable Displacements in Cape Riverne stay open after trading a scale.', + transform: (arg) => { + const numValue = Number(arg); + if (!Number.isNaN(numValue)) { + return `${numValue}s`; + } + return '???'; + }, }, 'MAIN.ROE_EXP_RATE': { - name: '', + name: 'RoE EXP Rate', description: '', + transform: (arg) => { + const numValue = Number(arg); + if (!Number.isNaN(numValue)) { + return `${numValue < 1 ? '' : '+'}${((numValue - 1) * 100).toFixed()}%`; + } + return '???'; + }, }, 'MAIN.RUNIC_DISK_SAVE': { - name: '', - description: '', - }, - 'MAIN.SERVER_MESSAGE': { - default: - 'Please visit https://github.com/LandSandBoat/server for the latest information on the project.\nThank you, and we hope you enjoy sailing the sands!', - name: '', - description: '', - }, - 'MAIN.SERVER_NAME': { - name: '', - description: '', + name: 'Runic Disk Save', + description: 'Allows anyone participating in Nyzul to save progress.', }, 'MAIN.SHOP_PRICE': { - name: '', + name: 'Shop Prices', description: '', + transform: (arg) => { + const numValue = Number(arg); + if (!Number.isNaN(numValue)) { + return `${numValue < 1 ? '' : '+'}${((numValue - 1) * 100).toFixed()}%`; + } + return '???'; + }, }, 'MAIN.SNEAK_INVIS_DURATION_MULTIPLIER': { - name: '', + name: 'Sneak/Invis Duration', description: '', + transform: (arg) => { + const numValue = Number(arg); + if (!Number.isNaN(numValue)) { + return `${numValue < 1 ? '' : '+'}${((numValue - 1) * 100).toFixed()}%`; + } + return '???'; + }, }, 'MAIN.SPARKS_RATE': { - name: '', + name: 'Sparks Rate', description: '', + transform: (arg) => { + const numValue = Number(arg); + if (!Number.isNaN(numValue)) { + return `${numValue < 1 ? '' : '+'}${((numValue - 1) * 100).toFixed()}%`; + } + return '???'; + }, }, 'MAIN.SPIKE_EFFECT_DURATION': { - name: '', - description: '', + name: 'Spikes Effect Duration', + description: 'Duration of RDM, BLM spikes effects.', + transform: (arg) => { + const numValue = Number(arg); + if (!Number.isNaN(numValue)) { + return `${numValue}s`; + } + return '???'; + }, }, 'MAIN.START_GIL': { - name: '', - description: '', + name: 'Starting Gil', + description: 'Amount of gil given to newly created characters.', }, 'MAIN.START_INVENTORY': { - name: '', - description: '', + name: 'Starting Inventory Size', + description: 'Starting inventory and satchel size.', }, 'MAIN.STONESKIN_CAP': { - name: '', - description: '', + name: 'Stoneskin Cap', + description: 'Soft cap for hp absorbed by stoneskin.', }, 'MAIN.SUBJOB_QUEST_LEVEL': { - name: '', - description: '', + name: 'Subjob Quest Level', + description: 'Minimum level to accept either subjob quest.', + transform: (arg) => { + return `Lv.${arg}`; + }, }, 'MAIN.TABS_RATE': { - name: '', + name: 'Tabs Rate', description: '', + transform: (arg) => { + const numValue = Number(arg); + if (!Number.isNaN(numValue)) { + return `${numValue < 1 ? '' : '+'}${((numValue - 1) * 100).toFixed()}%`; + } + return '???'; + }, }, 'MAIN.TIMELESS_HOURGLASS_COST': { - name: '', - description: '', - }, - 'MAIN.TRUST_ALTER_EGO_EXPO_MESSAGE': { - default: - '? ????? The Alter Ego Expo Campaign is active! ?????Trusts gain the benefits of Increased HP, MP, and Status Resistances!', - name: '', - description: '', - }, - 'MAIN.TRUST_ALTER_EGO_EXTRAVAGANZA_MESSAGE': { - default: - '? ????? The Alter Ego Extravaganza Campaign is active! ?????This is an excellent time to fill out your roster of Trusts!', - name: '', - description: '', + name: 'Timeless Hourglass Cost', + description: 'Refund for the timeless hourglass for Dynamis.', }, 'MAIN.UNLOCK_OUTPOST_WARPS': { - name: '', - description: '', + name: 'All Outposts', + description: 'New characters receive all outpost warps.', + transform: (arg) => { + return !!arg; + }, }, 'MAIN.USE_ADOULIN_WEAPON_SKILL_CHANGES': { - name: '', - description: '', + name: 'Adoulin Weapon Skill Calculations', + description: 'Uses new Adoulin weapon skill damage calculations.', }, 'MAIN.USE_OLD_CURE_FORMULA': { - name: '', - description: '', + name: 'Old Cure Formula', + description: 'Uses older cure formula.', }, 'MAIN.USE_OLD_MAGIC_DAMAGE': { - name: '', - description: '', + name: 'Old Magic Damage', + description: 'Uses older magic damage formulas.', }, 'MAIN.WEAPON_SKILL_POWER': { - name: '', + name: 'Weapon Skill Power', description: '', + transform: (arg) => { + const numValue = Number(arg); + if (!Number.isNaN(numValue)) { + return `${numValue < 1 ? '' : '+'}${((numValue - 1) * 100).toFixed()}%`; + } + return '???'; + }, }, 'MAIN.WEEKLY_EXCHANGE_LIMIT': { - name: '', - description: '', + name: 'Sparks Weekly Limit', + description: + 'Maximum amount of sparks and Unity accolades that can be spent per week.', }, 'MAP.ABILITY_RECAST_MULTIPLIER': { - name: '', + name: 'Ability Recast', description: '', + transform: (arg) => { + const numValue = Number(arg); + if (!Number.isNaN(numValue)) { + return `${numValue < 1 ? '' : '+'}${((numValue - 1) * 100).toFixed()}%`; + } + return '???'; + }, }, 'MAP.AH_BASE_FEE_SINGLE': { - name: '', - description: '', + name: 'AH Base Fee Single', + description: 'Base auction house fee for single items.', }, 'MAP.AH_BASE_FEE_STACKS': { - name: '', - description: '', + name: 'AH Base Fee Stack', + description: 'Base auction house fee for stacks.', }, 'MAP.AH_LIST_LIMIT': { - name: '', - description: '', + name: 'AH List Limit', + description: 'Max open listings per player.', + transform: (arg) => { + if (arg === 0) { + return 'unlimited'; + } + return arg; + }, }, 'MAP.AH_MAX_FEE': { - name: '', - description: '', + name: 'AH Max Fee', + description: 'Max auction house fee.', }, 'MAP.AH_TAX_RATE_SINGLE': { - name: '', + name: 'AH Tax Rate Single', description: '', + transform: (arg) => { + return `${arg}%`; + }, }, 'MAP.AH_TAX_RATE_STACKS': { - name: '', + name: 'AH Tax Rate Stack', description: '', + transform: (arg) => { + return `${arg}%`; + }, }, 'MAP.ALL_JOBS_WIDESCAN': { - name: '', - description: '', + name: 'All Jobs Widescan', + description: 'Jobs other than BST and RNG have widescan.', }, 'MAP.ALL_MOBS_GIL_BONUS': { - name: '', - description: '', + name: 'All Mobs Bonus Gil', + description: + 'All mobs drop this much extra gil per mob level even if they normally drop zero.', }, 'MAP.ALTER_EGO_HP_MULTIPLIER': { - name: '', + name: 'Trust HP', description: '', + transform: (arg) => { + const numValue = Number(arg); + if (!Number.isNaN(numValue)) { + return `${numValue < 1 ? '' : '+'}${((numValue - 1) * 100).toFixed()}%`; + } + return '???'; + }, }, 'MAP.ALTER_EGO_MP_MULTIPLIER': { - name: '', + name: 'Trust MP', description: '', + transform: (arg) => { + const numValue = Number(arg); + if (!Number.isNaN(numValue)) { + return `${numValue < 1 ? '' : '+'}${((numValue - 1) * 100).toFixed()}%`; + } + return '???'; + }, }, 'MAP.ALTER_EGO_SKILL_MULTIPLIER': { - name: '', + name: 'Trust Skill', description: '', + transform: (arg) => { + const numValue = Number(arg); + if (!Number.isNaN(numValue)) { + return `${numValue < 1 ? '' : '+'}${((numValue - 1) * 100).toFixed()}%`; + } + return '???'; + }, }, 'MAP.ALTER_EGO_STAT_MULTIPLIER': { - name: '', + name: 'Trust Stats', description: '', + transform: (arg) => { + const numValue = Number(arg); + if (!Number.isNaN(numValue)) { + return `${numValue < 1 ? '' : '+'}${((numValue - 1) * 100).toFixed()}%`; + } + return '???'; + }, }, 'MAP.ANTICHEAT_ENABLED': { - name: '', - description: '', - }, - 'MAP.ANTICHEAT_JAIL_DISABLE': { - name: '', - description: '', - }, - 'MAP.AUDIT_CHAT': { - name: '', - description: '', - }, - 'MAP.AUDIT_GM_CMD': { - name: '', - description: '', + name: 'Anticheat', + description: 'Server side anti-cheating measurements active.', }, 'MAP.AUDIT_LINKSHELL': { - name: '', + name: 'Logs Linkshell Chat', description: '', }, 'MAP.AUDIT_PARTY': { - name: '', + name: 'Logs Party Chat', description: '', }, 'MAP.AUDIT_SAY': { - name: '', + name: 'Logs Say Chat', description: '', }, 'MAP.AUDIT_SHOUT': { - name: '', + name: 'Logs Shout Chat', description: '', }, 'MAP.AUDIT_TELL': { - name: '', + name: 'Logs Tells', description: '', }, 'MAP.AUDIT_UNITY': { - name: '', + name: 'Logs Unity Chat', description: '', }, 'MAP.AUDIT_YELL': { - name: '', + name: 'Logs Yells', description: '', }, 'MAP.BATTLE_CAP_TWEAK': { - name: '', - description: '', + name: 'Battlefield Level Cap', + description: + 'Globally adjusts ALL battlefield level caps by this many levels.', + transform: (arg) => { + const numValue = Number(arg); + if (!Number.isNaN(numValue)) { + return `${numValue < 0 ? '' : '+'}${numValue}`; + } + return '???'; + }, }, 'MAP.BLOCK_OLD_SKILLUP_STYLE': { - name: '', - description: '', - }, - 'MAP.BLOCK_TELL_TO_HIDDEN_GM': { - name: '', - description: '', + name: 'Old Block Skill Increase', + description: 'Allows block to skill up regardless of the action occurring.', }, 'MAP.BLOOD_PACT_SHARED_TIMER': { - name: '', - description: '', + name: 'Blood Pact Shared Timer', + description: '"Blood Pact: Rage" and "Blood Pact: Ward" share a timer.', }, 'MAP.CAPACITY_RATE': { - name: '', + name: 'Capacity Points Rate', description: '', + transform: (arg) => { + const numValue = Number(arg); + if (!Number.isNaN(numValue)) { + return `${numValue < 1 ? '' : '+'}${((numValue - 1) * 100).toFixed()}%`; + } + return '???'; + }, }, 'MAP.CRAFT_AMOUNT_MULTIPLIER': { - name: '', + name: 'Craft Skill Increase', description: '', + transform: (arg) => { + const numValue = Number(arg); + if (!Number.isNaN(numValue)) { + return `${numValue < 1 ? '' : '+'}${((numValue - 1) * 100).toFixed()}%`; + } + return '???'; + }, }, 'MAP.CRAFT_CHANCE_MULTIPLIER': { - name: '', + name: 'Craft Skill Increase Rate', description: '', + transform: (arg) => { + const numValue = Number(arg); + if (!Number.isNaN(numValue)) { + return `${numValue < 1 ? '' : '+'}${((numValue - 1) * 100).toFixed()}%`; + } + return '???'; + }, }, 'MAP.CRAFT_COMMON_CAP': { - name: '', - description: '', + name: 'Craft Common Cap', + description: + 'Craft level limit from which specialization points beginning to count.', + transform: (arg) => { + const numValue = Number(arg); + if (!Number.isNaN(numValue)) { + return `${(numValue / 10).toFixed()}`; + } + return '???'; + }, }, 'MAP.CRAFT_MODERN_SYSTEM': { - name: '', - description: '', + name: 'Original Crafting System', + description: 'Uses original skill up rates and margins.', + transform: (arg) => { + return !arg; + }, }, 'MAP.CRAFT_SPECIALIZATION_POINTS': { - name: '', - description: '', + name: 'Craft Specialization Points', + description: 'Amount of points allowed in crafts over the common cap.', + transform: (arg) => { + const numValue = Number(arg); + if (!Number.isNaN(numValue)) { + return `${(numValue / 10).toFixed()}`; + } + return '???'; + }, }, 'MAP.DESPAWN_JUGPETS_BELOW_MINIMUM_LEVEL': { - name: '', - description: '', + name: 'Despawn Jug Pets When Capped', + description: + 'Despawns jug pets that have a minimum level below level sync or zone level restriction.', }, 'MAP.DISABLE_GEAR_SCALING': { - name: '', - description: '', + name: 'Gear Scaling', + description: + 'Ability to equip higher level gear when level cap/sync effect is on.', + transform: (arg) => { + return !arg; + }, }, 'MAP.DROP_RATE_MULTIPLIER': { - name: '', + name: 'Mob Drop Rate', description: '', + transform: (arg) => { + const numValue = Number(arg); + if (!Number.isNaN(numValue)) { + return `${numValue < 1 ? '' : '+'}${((numValue - 1) * 100).toFixed()}%`; + } + return '???'; + }, }, 'MAP.ENABLE_ITEM_RECYCLE_BIN': { - name: '', + name: 'Recycling Bin', description: '', }, 'MAP.ENMITY_CAP': { - name: '', + name: 'Enmity Cap', description: '', }, 'MAP.EXP_LOSS_LEVEL': { - name: '', - description: '', + name: 'EXP Loss', + description: 'Minimum level at which experience points can be lost.', + transform: (arg) => { + return `Lv.${arg}`; + }, }, 'MAP.EXP_LOSS_RATE': { - name: '', + name: 'EXP Loss Rate', description: '', + transform: (arg) => { + const numValue = Number(arg); + if (!Number.isNaN(numValue)) { + return `${numValue < 1 ? '' : '+'}${((numValue - 1) * 100).toFixed()}%`; + } + return '???'; + }, }, 'MAP.EXP_PARTY_GAP_NO_EXP': { - name: '', - description: '', + name: 'EXP Max Party Level Gap', + description: + "A party member's experience points are nullified if the level difference with the highest-level party member exceeds this value.", }, 'MAP.EXP_PARTY_GAP_PENALTIES': { - name: '', - description: '', + name: 'EXP Party Level Penalties', + description: 'Penalizes EXP based on level differences in the party.', }, 'MAP.EXP_RATE': { - name: '', - description: '', + name: 'EXP Rate', + description: 'Does not account for EXP table changes.', + transform: (arg) => { + const numValue = Number(arg); + if (!Number.isNaN(numValue)) { + return `${numValue < 1 ? '' : '+'}${((numValue - 1) * 100).toFixed()}%`; + } + return '???'; + }, }, 'MAP.EXP_RETAIN': { - name: '', - description: '', + name: 'EXP Retained on Death', + description: 'Percentage of experience normally lost kept upon death.', + transform: (arg) => { + const numValue = Number(arg); + if (!Number.isNaN(numValue)) { + return `+${numValue * 100}%`; + } + return '???'; + }, }, 'MAP.FAME_MULTIPLIER': { - name: '', + name: 'Fame Rate', description: '', + transform: (arg) => { + const numValue = Number(arg); + if (!Number.isNaN(numValue)) { + return `${numValue < 1 ? '' : '+'}${((numValue - 1) * 100).toFixed()}%`; + } + return '???'; + }, }, 'MAP.FELLOW_TP_MULTIPLIER': { - name: '', + name: 'Fellow TP', description: '', + transform: (arg) => { + const numValue = Number(arg); + if (!Number.isNaN(numValue)) { + return `${numValue < 1 ? '' : '+'}${((numValue - 1) * 100).toFixed()}%`; + } + return '???'; + }, }, 'MAP.FISHING_ENABLE': { - name: '', + name: 'Fishing', description: '', }, 'MAP.FISHING_SKILL_MULTIPLIER': { - name: '', + name: 'Fishing Skill Increase Rate', description: '', + transform: (arg) => { + const numValue = Number(arg); + if (!Number.isNaN(numValue)) { + return `${numValue < 1 ? '' : '+'}${((numValue - 1) * 100).toFixed()}%`; + } + return '???'; + }, }, 'MAP.GARDEN_DAY_MATTERS': { - name: '', + name: 'Gardening Day Matters', description: '', }, 'MAP.GARDEN_MH_AURA_MATTERS': { - name: '', + name: 'Gardening Aura Matters', description: '', }, 'MAP.GARDEN_MOONPHASE_MATTERS': { - name: '', + name: 'Gardening Moon Phase Matters', description: '', }, 'MAP.GARDEN_POT_MATTERS': { - name: '', + name: 'Gardening Pot Matters', description: '', }, 'MAP.GUARD_OLD_SKILLUP_STYLE': { - name: '', - description: '', + name: 'Old Guard Skill Increase', + description: 'Allows guard to skill up regardless of the action occurring.', }, 'MAP.HEALING_TICK_DELAY': { - name: '', - description: '', + name: 'Healing Tick', + description: 'Delay between healing ticks.', + transform: (arg) => { + return `${arg}s`; + }, }, 'MAP.INCLUDE_MOB_SJ': { - name: '', - description: '', + name: 'Mob Subjob Adjust', + description: 'Mob subjobs are affected by subjob ratio.', }, 'MAP.KEEP_JUGPET_THROUGH_ZONING': { - name: '', + name: 'Keep Jug Pets Through Zone', description: '', }, 'MAP.LEVEL_SYNC_ENABLE': { name: 'Level Sync', description: '', }, - 'MAP.LIGHTLUGGAGE_BLOCK': { - name: '', - description: '', - }, 'MAP.LV_CAP_MISSION_BCNM': { - name: '', + name: 'Level Cap Mission BCNM', description: '', }, 'MAP.MAX_GIL_BONUS': { - name: '', - description: '', + name: 'Max Gil Bonus', + description: + 'Maximum total bonus gil that can be dropped. (All Mobs Bonus Gil)', }, 'MAP.MAX_MERIT_POINTS': { - name: '', - description: '', - }, - 'MAP.MAX_TIME_LASTUPDATE': { - name: '', - description: '', + name: 'Merit Points Cap', + description: 'Initial max allowed merits points players can hold.', }, 'MAP.MINIMUM_LEVEL_CONQUEST_INFUENCE_LOSS': { - name: '', - description: '', + name: 'Conquest Influence Loss', + description: + 'Minimum level at which regional influence is lost in conquest when a player dies.', + transform: (arg) => { + return `Lv.${arg}`; + }, }, 'MAP.MOB_ADDITIONAL_TIME_TO_DEAGGRO': { - name: '', - description: '', + name: 'Mob Additional Despawn Time', + description: 'Extra time before a mob despawns after deaggro.', + transform: (arg) => { + return `+${arg}s`; + }, }, 'MAP.MOB_GIL_MULTIPLIER': { - name: '', + name: 'Mob Gil Drops', description: '', + transform: (arg) => { + const numValue = Number(arg); + if (!Number.isNaN(numValue)) { + return `${numValue < 1 ? '' : '+'}${((numValue - 1) * 100).toFixed()}%`; + } + return '???'; + }, }, 'MAP.MOB_HP_MULTIPLIER': { - name: '', + name: 'Mob HP', description: '', + transform: (arg) => { + const numValue = Number(arg); + if (!Number.isNaN(numValue)) { + return `${numValue < 1 ? '' : '+'}${((numValue - 1) * 100).toFixed()}%`; + } + return '???'; + }, }, 'MAP.MOB_MP_MULTIPLIER': { - name: '', + name: 'Mob MP', description: '', + transform: (arg) => { + const numValue = Number(arg); + if (!Number.isNaN(numValue)) { + return `${numValue < 1 ? '' : '+'}${((numValue - 1) * 100).toFixed()}%`; + } + return '???'; + }, }, 'MAP.MOB_NO_DESPAWN': { - name: '', - description: '', + name: 'Mob No Despawn', + description: 'Allows mobs to walk back home instead of despawning.', }, 'MAP.MOB_SPEED_MOD': { - name: '', - description: '', + name: 'Mob Aggro Speed', + description: + 'Modifier to apply to monster speed after aggro as a percentage of retail speed.', + transform: (arg) => { + const numValue = Number(arg); + if (!Number.isNaN(numValue)) { + return `${numValue < 0 ? '' : '+'}${((50 + numValue) / 50 - 1) * 100}%`; + } + return '???'; + }, }, 'MAP.MOB_STAT_MULTIPLIER': { - name: '', + name: 'Mob Stats', description: '', + transform: (arg) => { + const numValue = Number(arg); + if (!Number.isNaN(numValue)) { + return `${numValue < 1 ? '' : '+'}${((numValue - 1) * 100).toFixed()}%`; + } + return '???'; + }, }, 'MAP.MOB_TP_MULTIPLIER': { - name: '', + name: 'Mob TP', description: '', + transform: (arg) => { + const numValue = Number(arg); + if (!Number.isNaN(numValue)) { + return `${numValue < 1 ? '' : '+'}${((numValue - 1) * 100).toFixed()}%`; + } + return '???'; + }, }, 'MAP.MOUNT_SPEED_MOD': { - name: '', - description: '', + name: 'Mount Speed', + description: 'Mount speed compared to retail.', + transform: (arg) => { + const numValue = Number(arg); + if (!Number.isNaN(numValue)) { + return `${numValue < 0 ? '' : '+'}${((40 + numValue) / 40 - 1) * 100}%`; + } + return '???'; + }, }, 'MAP.NM_HP_MULTIPLIER': { - name: '', + name: 'NM HP', description: '', + transform: (arg) => { + const numValue = Number(arg); + if (!Number.isNaN(numValue)) { + return `${numValue < 1 ? '' : '+'}${((numValue - 1) * 100).toFixed()}%`; + } + return '???'; + }, }, 'MAP.NM_MP_MULTIPLIER': { - name: '', + name: 'NM MP', description: '', + transform: (arg) => { + const numValue = Number(arg); + if (!Number.isNaN(numValue)) { + return `${numValue < 1 ? '' : '+'}${((numValue - 1) * 100).toFixed()}%`; + } + return '???'; + }, }, 'MAP.NM_STAT_MULTIPLIER': { - name: '', - description: '', - }, - 'MAP.PACKETGUARD_ENABLED': { - name: '', + name: 'NM Stats', description: '', + transform: (arg) => { + const numValue = Number(arg); + if (!Number.isNaN(numValue)) { + return `${numValue < 1 ? '' : '+'}${((numValue - 1) * 100).toFixed()}%`; + } + return '???'; + }, }, 'MAP.PARRY_OLD_SKILLUP_STYLE': { - name: '', - description: '', + name: 'Old Parry Skill Increase', + description: 'Allows parry to skill up regardless of the action occurring.', }, 'MAP.PET_TP_MULTIPLIER': { - name: '', + name: 'Pet TP', description: '', + transform: (arg) => { + const numValue = Number(arg); + if (!Number.isNaN(numValue)) { + return `${numValue < 1 ? '' : '+'}${((numValue - 1) * 100).toFixed()}%`; + } + return '???'; + }, }, 'MAP.PLAYER_HP_MULTIPLIER': { - name: '', + name: 'Player HP', description: '', + transform: (arg) => { + const numValue = Number(arg); + if (!Number.isNaN(numValue)) { + return `${numValue < 1 ? '' : '+'}${((numValue - 1) * 100).toFixed()}%`; + } + return '???'; + }, }, 'MAP.PLAYER_MP_MULTIPLIER': { - name: '', + name: 'Player MP', description: '', + transform: (arg) => { + const numValue = Number(arg); + if (!Number.isNaN(numValue)) { + return `${numValue < 1 ? '' : '+'}${((numValue - 1) * 100).toFixed()}%`; + } + return '???'; + }, }, 'MAP.PLAYER_STAT_MULTIPLIER': { - name: '', + name: 'Player Stats', description: '', + transform: (arg) => { + const numValue = Number(arg); + if (!Number.isNaN(numValue)) { + return `${numValue < 1 ? '' : '+'}${((numValue - 1) * 100).toFixed()}%`; + } + return '???'; + }, }, 'MAP.PLAYER_TP_MULTIPLIER': { - name: '', + name: 'Player TP', description: '', + transform: (arg) => { + const numValue = Number(arg); + if (!Number.isNaN(numValue)) { + return `${numValue < 1 ? '' : '+'}${((numValue - 1) * 100).toFixed()}%`; + } + return '???'; + }, }, 'MAP.PREVENT_UNENGAGED_WS': { - name: '', - description: '', - }, - 'MAP.REPORT_LUA_ERRORS_TO_PLAYER_LEVEL': { - name: '', - description: '', - }, - 'MAP.SETVAR_RETRY_MAX': { - name: '', + name: 'Prevent Unengaged Weapon Skills', description: '', }, 'MAP.SJ_MP_DIVISOR': { - name: '', - description: '', + name: 'Subjob MP', + description: 'The amount of MP a subjob provides to the main job.', + transform: (arg) => { + const numValue = Number(arg); + if (!Number.isNaN(numValue)) { + return `${(1 / numValue) * 100}%`; + } + return '???'; + }, }, 'MAP.SKILLUP_AMOUNT_MULTIPLIER': { - name: '', - description: '', + name: 'Combat Skill Increase', + description: 'Skill increase amount.', + transform: (arg) => { + const numValue = Number(arg); + if (!Number.isNaN(numValue)) { + return `${numValue < 1 ? '' : '+'}${((numValue - 1) * 100).toFixed()}%`; + } + return '???'; + }, }, 'MAP.SKILLUP_BLOODPACT': { - name: '', - description: '', + name: 'Blood Pact Skill Increase', + description: 'Allows skill ups from blood pacts.', }, 'MAP.SKILLUP_CHANCE_MULTIPLIER': { - name: '', + name: 'Combat Skill Increase Rate', description: '', + transform: (arg) => { + const numValue = Number(arg); + if (!Number.isNaN(numValue)) { + return `${numValue < 1 ? '' : '+'}${((numValue - 1) * 100).toFixed()}%`; + } + return '???'; + }, }, 'MAP.SPEED_MOD': { - name: 'Speed', + name: 'Player Speed', description: 'Player speed compared to retail.', + transform: (arg) => { + const numValue = Number(arg); + if (!Number.isNaN(numValue)) { + return `${numValue < 0 ? '' : '+'}${((50 + numValue) / 50 - 1) * 100}%`; + } + return '???'; + }, }, 'MAP.SUBJOB_RATIO': { - name: '', - description: '', + name: 'Subjob Ratio', + description: 'Subjob to main job ratio.', + transform: (arg) => { + let ret = '???'; + switch (arg) { + case 0: + ret = 'none'; + break; + case 1: + ret = '1:2'; + break; + case 2: + ret = '2:3'; + break; + case 3: + ret = '1:1'; + break; + default: + } + return ret; + }, }, 'MAP.TRUST_TP_MULTIPLIER': { - name: '', - description: '', - }, - 'MAP.VANADIEL_TIME_EPOCH': { - name: '', + name: 'Trust TP', description: '', + transform: (arg) => { + const numValue = Number(arg); + if (!Number.isNaN(numValue)) { + return `${numValue < 1 ? '' : '+'}${((numValue - 1) * 100).toFixed()}%`; + } + return '???'; + }, }, 'MAP.WS_POINTS_BASE': { - name: '', - description: '', + name: 'Weapon Skill Point Base', + description: + 'Weapon skill point base (before skillchain) for breaking latent.', }, 'MAP.WS_POINTS_SKILLCHAIN': { - name: '', - description: '', + name: 'Weapon Skill Point Skillchain', + description: 'Weapon skill points per skillchain element.', }, 'MAP.YELL_COOLDOWN': { - name: '', - description: '', - }, - 'SEARCH.DEBUG_OUT_PACKETS': { - name: '', - description: '', + name: 'Yell Cooldown', + description: 'Minimum time between uses of yell command.', + transform: (arg) => { + return `${arg}s`; + }, }, 'SEARCH.EXPIRE_AUCTIONS': { - name: '', - description: '', + name: 'Expire Auctions', + description: 'Expires Auction House listings.', }, 'SEARCH.EXPIRE_DAYS': { - name: '', - description: '', + name: 'Auction List Duration', + description: 'Expires items older than this number of days.', + transform: (arg) => { + return `${arg} day`; + }, }, 'SEARCH.EXPIRE_INTERVAL': { - name: '', - description: '', + name: 'Auction Expire Interval', + description: 'Interval server checks for expired auctions.', + transform: (arg) => { + const numValue = Number(arg); + if (!Number.isNaN(numValue)) { + return `${numValue / 60} min`; + } + return '???'; + }, }, 'SEARCH.OMIT_NO_HISTORY': { - name: '', - description: '', + name: 'AH Omit No History', + description: + 'Items with no listing history are omitted from auction house results.', }, }; diff --git a/client/src/images/copy-image.png b/client/src/images/copy-image.png new file mode 100644 index 0000000000000000000000000000000000000000..de425f36da3e61e21d6798e7cfe33b62487cca92 GIT binary patch literal 1364 zcmV-a1*`grP)Px)4oO5oR9FeUm#uT_I26SL4Fv@S1qB5K1q}@i4Gjea4Gj$q0|NsKA1r)u|C6(O zj^oxz({}rIW;=7owPXAKbU)6OPp41*nx?^Lwm}}QeP+tnh%Yc6AkjI43dRS+)!e}$H>jl#L zNf0%j|2sfgMl9a|7-Qj01R-dQMvWJ20G%Sh8sVbu?5o<~ja(g9I;_3I|5*yY1MoHo zf~h(hYv;?_V3oUM5r>FwZnx0aguwi_4lY_)YgO&Rq`L^P2pNGc6lCAstXE2s?+{#i zUK_^{sl`mAYG=MvsI$?R4Q7;waI$*e6uWQA8nXz71)VU(YO@IX9#s`R3 zwdYox5A8_+u9yJJb;fk5DCbemc|5?}ISCV^;Kny` zqCV`Ypg<+p-lu>7#u$VYkXnX$4|PC0W<}uqdzBurD`FSnqeDnu&oCWKo3Tc9(TA$l zw|4^%X8~1kIQM94R&mm4ux!6pM~V@RinxcH0Ic>O3(?*h(t`L7KnTJx0@gY?PPP@q zQ3*>}m4fKnKO05_pmR>VG(?Q&QTb;Y*ab`r(?bZ3-J=!|h?9|GMEj$OsFllkQY*3( zSp75tg>o(eV~Pmj+*T6=*dwu}VpSA& z2o?IB*W1bC%Bg;Ykn`+S4-C1_8w6F75KPGq!Qn&)g0SgU3)~@X5R_=O`?FMVd&R9* zS6i&V7Fk;|TyokMvglNJ-sl>^rfZd{HM7$e?G1pDxIwTM2tfdf(9vIrEdcj{96U@x>Y`RH=X>6?TQ-vGtw<{rsaphi+Z%x0 zx`F_EM|=-ZvR1{Hb0#qC&jEr4R=pH;2h+zW9swv$Y|Cc=AJRZau?wnf?Sj`)v;Rwg zb<+nHB%js2hX_0Zum*UI^Wg)4^A`QoNo+KD{w08R%-5o4`Ho`*f(>%G*MX*@GW%UrB`Eytr-M{h;pn5CK*jR{QQ%R5M1%m)PlE{?gw5Y#QS}> zZ^iuv9l6Qb_Y2!B;5^{9(^uaFd%wRO?_PR1dDu4`emVTxXz$Pc|88G9KRo(>{`)sQ WzB9Qr-6%l-0000 { - if ( - searchState.name.value.length > 0 && - typeof server.settings['MAIN.SERVER_NAME'] === 'string' - ) { - const serverName = server.settings['MAIN.SERVER_NAME'].toLowerCase(); + if (searchState.name.value.length > 0) { + const serverName = server.name.toLowerCase(); if (!serverName.includes(searchState.name.value.toLowerCase())) { return false; } } - if (typeof server.settings['MAIN.MAX_LEVEL'] === 'number') { - if (server.settings['MAIN.MAX_LEVEL'] < searchState.maxLevel.value[0]) { - return false; - } - if (server.settings['MAIN.MAX_LEVEL'] > searchState.maxLevel.value[1]) { - return false; - } + if (server.max_level < searchState.maxLevel.value[0]) { + return false; + } + if (server.max_level > searchState.maxLevel.value[1]) { + return false; } if ( searchState.trusts.value && - typeof server.settings['MAIN.ENABLE_TRUST_CASTING'] === 'number' + typeof server.customizations['MAIN.ENABLE_TRUST_CASTING'] === 'number' ) { - const serverTrusts = server.settings['MAIN.ENABLE_TRUST_CASTING'] === 1; + const serverTrusts = + server.customizations['MAIN.ENABLE_TRUST_CASTING'] === 1; const searchEnabled = searchState.trusts.value.includes('enabled'); const searchDisabled = searchState.trusts.value.includes('disabled'); if (serverTrusts && searchDisabled && !searchEnabled) { @@ -81,9 +77,9 @@ export default function Home({ if ( searchState.levelSync.value && - typeof server.settings['MAP.LEVEL_SYNC_ENABLE'] === 'boolean' + typeof server.customizations['MAP.LEVEL_SYNC_ENABLE'] === 'boolean' ) { - const serverLevelSync = server.settings['MAP.LEVEL_SYNC_ENABLE']; + const serverLevelSync = server.customizations['MAP.LEVEL_SYNC_ENABLE']; const searchEnabled = searchState.levelSync.value.includes('enabled'); const searchDisabled = searchState.levelSync.value.includes('disabled'); if (serverLevelSync && searchDisabled && !searchEnabled) { @@ -98,19 +94,19 @@ export default function Home({ const searchNoneEnabled = searchState.expansions.value.includes('none'); const searchRotzEnabled = searchState.expansions.value.includes('rotz'); const serverRotzEnabled = - server.settings['LOGIN.RISE_OF_ZILART'] === true; + server.customizations['LOGIN.RISE_OF_ZILART'] === true; const searchCopEnabled = searchState.expansions.value.includes('cop'); const serverCopEnabled = - server.settings['LOGIN.CHAINS_OF_PROMATHIA'] === true; + server.customizations['LOGIN.CHAINS_OF_PROMATHIA'] === true; const searchToauEnabled = searchState.expansions.value.includes('toau'); const serverToauEnabled = - server.settings['LOGIN.TREASURES_OF_AHT_URGHAN'] === true; + server.customizations['LOGIN.TREASURES_OF_AHT_URGHAN'] === true; const searchWotgEnabled = searchState.expansions.value.includes('wotg'); const serverWotgEnabled = - server.settings['LOGIN.WINGS_OF_THE_GODDESS'] === true; + server.customizations['LOGIN.WINGS_OF_THE_GODDESS'] === true; const searchSoaEnabled = searchState.expansions.value.includes('soa'); const serverSoaEnabled = - server.settings['LOGIN.SEEKERS_OF_ADOULIN'] === true; + server.customizations['LOGIN.SEEKERS_OF_ADOULIN'] === true; if ( searchNoneEnabled && (serverRotzEnabled || @@ -139,27 +135,22 @@ export default function Home({ } if (searchState.multibox.value) { - const serverMultibox = server.settings['LOGIN.LOGIN_LIMIT']; - if (typeof serverMultibox === 'number') { - if ( - searchState.multibox.value.includes('none') && - serverMultibox !== 1 - ) { - return false; - } - if ( - searchState.multibox.value.includes('unlimited') && - serverMultibox !== 0 - ) { - return false; - } - if ( - searchState.multibox.value.includes('limited') && - !searchState.multibox.value.includes('unlimited') && - serverMultibox < 2 - ) { - return false; - } + const serverMultibox = server.login_limit; + if (searchState.multibox.value.includes('none') && serverMultibox !== 1) { + return false; + } + if ( + searchState.multibox.value.includes('unlimited') && + serverMultibox !== 0 + ) { + return false; + } + if ( + searchState.multibox.value.includes('limited') && + !searchState.multibox.value.includes('unlimited') && + serverMultibox < 2 + ) { + return false; } }