1 /// NWScript functions and types implemented in D 2 module nwn.nwscript.functions; 3 4 import std.stdint; 5 import std.array; 6 import std.string; 7 import std.conv: to; 8 import std.meta; 9 10 import nwn.tlk; 11 static import nwn.gff; 12 static import nwn.fastgff; 13 import nwn.twoda; 14 import nwn.nwscript.constants; 15 import nwn.nwscript.resources; 16 import nwn.nwscript.extensions; 17 18 public import nwn.types; 19 20 21 /// 22 enum OBJECT_INVALID = NWObject.max; 23 24 25 /// 26 NWString GetName(ST)(ref ST objectGff) if(isGffStruct!ST) { 27 mixin(ImportGffLib!ST); 28 scope resolv = getStrRefResolver(); 29 if(auto fname = "FirstName" in objectGff){ 30 auto firstName = fname.get!GffLocString.resolve(resolv); 31 auto lastName = objectGff["LastName"].get!GffLocString.resolve(resolv); 32 if(lastName != "") 33 return [firstName, lastName].join(" "); 34 return firstName; 35 } 36 if(auto localizedName = "LocalizedName" in objectGff) 37 return localizedName.get!GffLocString.resolve(resolv); 38 else if(auto locName = "LocName" in objectGff) 39 return locName.get!GffLocString.resolve(resolv); 40 return ""; 41 } 42 /// 43 NWString GetFirstName(ST)(ref ST objectGff) if(isGffStruct!ST) { 44 mixin(ImportGffLib!ST); 45 if("FirstName" in objectGff) 46 return objectGff["FirstName"].get!GffLocString.resolve(getStrRefResolver()); 47 else if("LocalizedName" in objectGff) 48 return objectGff["LocalizedName"].get!GffLocString.resolve(getStrRefResolver()); 49 return ""; 50 } 51 /// 52 NWString GetLastName(ST)(ref ST objectGff) if(isGffStruct!ST) { 53 mixin(ImportGffLib!ST); 54 if("LastName" in objectGff) 55 return objectGff["LastName"].get!GffLocString.resolve(getStrRefResolver()); 56 return ""; 57 } 58 59 /// 60 void SetFirstName(ref nwn.gff.GffStruct objectGff, in NWString name){ 61 import nwn.gff; 62 int32_t language = getStrRefResolver().standartTable.language; 63 if("FirstName" in objectGff) 64 objectGff["FirstName"].get!GffLocString.strings = [language * 2: name]; 65 else if("LocalizedName" in objectGff) 66 objectGff["LocalizedName"].get!GffLocString.strings = [language * 2: name]; 67 else assert(0, "No FirstName GFF field"); 68 } 69 /// 70 void SetLastName(ref nwn.gff.GffStruct objectGff, in NWString name){ 71 import nwn.gff; 72 assert("LastName" in objectGff, "No LastName GFF field"); 73 int32_t language = 0; 74 if(getStrRefResolver() !is null) 75 language = getStrRefResolver().standartTable.language; 76 objectGff["LastName"].get!GffLocString.strings = [language * 2: name]; 77 } 78 79 unittest{ 80 immutable dogeUtc = cast(immutable ubyte[])import("doge.utc"); 81 82 immutable dialogTlk = cast(immutable ubyte[])import("dialog.tlk"); 83 immutable userTlk = cast(immutable ubyte[])import("user.tlk"); 84 initStrRefResolver(new StrRefResolver( 85 new Tlk(dialogTlk), 86 new Tlk(userTlk) 87 )); 88 89 { 90 auto obj = new nwn.gff.Gff(dogeUtc).root; 91 92 assert(GetName(obj) == "Doge"); 93 94 SetFirstName(obj, "Sir doge"); 95 assert(GetName(obj) == "Sir doge"); 96 97 SetLastName(obj, "the mighty"); 98 assert(GetName(obj) == "Sir doge the mighty"); 99 assert(GetFirstName(obj) == "Sir doge"); 100 assert(GetLastName(obj) == "the mighty"); 101 } 102 { 103 auto obj = new nwn.fastgff.FastGff(dogeUtc).root; 104 assert(GetName(obj) == "Doge"); 105 } 106 107 } 108 109 110 /// 111 bool GetIsItemPropertyValid(NWItemproperty ip){ 112 return ip.type != uint16_t.max; 113 } 114 115 116 117 private template TypeToNWTypeConst(T){ 118 static if (is(T == NWInt)) enum TypeToNWTypeConst = 1; 119 else static if(is(T == NWFloat)) enum TypeToNWTypeConst = 2; 120 else static if(is(T == NWString)) enum TypeToNWTypeConst = 3; 121 else static if(is(T == NWObject)) enum TypeToNWTypeConst = 4; 122 else static if(is(T == NWLocation)) enum TypeToNWTypeConst = 5; 123 else static assert(0, "Invalid type"); 124 } 125 126 127 private void SetLocal(T)(ref nwn.gff.GffStruct oObject, NWString sVarName, T value){ 128 import nwn.gff; 129 if("VarTable" in oObject){ 130 foreach(ref var ; oObject["VarTable"].get!GffList){ 131 // TODO: check behaviour when setting two variables with the same name and different types 132 if(var["Name"].get!GffString == sVarName){ 133 var["Type"].get!GffDWord = TypeToNWTypeConst!T; 134 static if (is(T == NWInt)) var["Value"].get!GffInt = value; 135 else static if(is(T == NWFloat)) var["Value"].get!GffFloat = value; 136 else static if(is(T == NWString)) var["Value"].get!GffString = value; 137 else static if(is(T == NWObject)) var["Value"].get!GffDWord = value; 138 else static if(is(T == NWLocation)) static assert(0, "Not implemented"); 139 else static assert(0, "Invalid type"); 140 return; 141 } 142 } 143 } 144 else{ 145 oObject["VarTable"] = GffList(); 146 } 147 148 auto var = nwn.gff.GffStruct(); 149 var["Name"] = sVarName; 150 var["Type"] = GffDWord(TypeToNWTypeConst!T); 151 static if (is(T == NWInt)) var["Value"] = GffInt(value); 152 else static if(is(T == NWFloat)) var["Value"] = GffFloat(value); 153 else static if(is(T == NWString)) var["Value"] = value; 154 else static if(is(T == NWObject)) var["Value"] = GffDWord(value); 155 else static if(is(T == NWLocation)) static assert(0, "Not implemented"); 156 else static assert(0, "Invalid type"); 157 158 oObject["VarTable"].get!GffList ~= var; 159 } 160 161 /// 162 alias SetLocalInt = SetLocal!NWInt; 163 /// 164 alias SetLocalFloat = SetLocal!NWFloat; 165 /// 166 alias SetLocalString = SetLocal!NWString; 167 /// 168 alias SetLocalObject = SetLocal!NWObject; 169 /// 170 //alias SetLocalLocation = SetLocal!NWLocation; 171 172 173 private T GetLocal(T, ST)(ref ST oObject, NWString sVarName) if(isGffStruct!ST) { 174 mixin(ImportGffLib!ST); 175 if("VarTable" in oObject){ 176 foreach(var ; oObject["VarTable"].get!GffList){ 177 if(var["Name"].get!GffString == sVarName && var["Type"].get!GffDWord == TypeToNWTypeConst!T){ 178 static if (is(T == NWInt)) return var["Value"].get!GffInt; 179 else static if(is(T == NWFloat)) return var["Value"].get!GffFloat; 180 else static if(is(T == NWString)) return var["Value"].get!GffString; 181 else static if(is(T == NWObject)) return var["Value"].get!GffDWord; 182 else static if(is(T == NWLocation)) static assert(0, "Not implemented"); 183 else static assert(0, "Invalid type"); 184 } 185 } 186 } 187 return NWInitValue!T; 188 } 189 /// 190 NWInt GetLocalInt(ST)(ref ST oObject, NWString sVarName){ return GetLocal!NWInt(oObject, sVarName); }; 191 /// 192 NWFloat GetLocalFloat(ST)(ref ST oObject, NWString sVarName){ return GetLocal!NWFloat(oObject, sVarName); }; 193 /// 194 NWString GetLocalString(ST)(ref ST oObject, NWString sVarName){ return GetLocal!NWString(oObject, sVarName); }; 195 /// 196 NWObject GetLocalObject(ST)(ref ST oObject, NWString sVarName){ return GetLocal!NWObject(oObject, sVarName); }; 197 /// 198 //alias GetLocalLocation = GetLocal!NWLocation; 199 200 201 unittest{ 202 import std.math; 203 immutable dogeUtc = cast(immutable ubyte[])import("doge.utc"); 204 { 205 auto obj = new nwn.gff.Gff(dogeUtc).root; 206 207 SetLocalInt(obj, "TestInt", 5); 208 SetLocalFloat(obj, "TestFloat", 5.3); 209 SetLocalString(obj, "TestString", "Hello"); 210 211 assert(GetLocalInt(obj, "TestInt") == 5); 212 213 assert(approxEqual(GetLocalFloat(obj, "TestFloat"), 5.3f)); 214 assert(GetLocalString(obj, "TestString") == "Hello"); 215 assert(GetLocalString(obj, "yolooo") == ""); 216 assert(GetLocalObject(obj, "TestInt") == OBJECT_INVALID); 217 218 DeleteLocalInt(obj, "TestString"); 219 assert(GetLocalString(obj, "TestString") == "Hello"); 220 DeleteLocalString(obj, "TestString"); 221 assert(GetLocalString(obj, "TestString") == ""); 222 } 223 { 224 auto obj = new nwn.fastgff.FastGff(dogeUtc).root; 225 assert(GetLocalInt(obj, "aaabbb") == 0); 226 } 227 228 } 229 230 private void DeleteLocal(T)(ref nwn.gff.GffStruct oObject, NWString sVarName){ 231 import nwn.gff; 232 import std.algorithm: remove; 233 if("VarTable" in oObject){ 234 foreach(i, ref var ; oObject["VarTable"].get!GffList){ 235 if(var["Name"].get!GffString == sVarName && var["Type"].get!GffDWord == TypeToNWTypeConst!T){ 236 oObject["VarTable"].get!GffList.children = oObject["VarTable"].get!GffList.children.remove(i); 237 return; 238 } 239 } 240 } 241 } 242 /// 243 alias DeleteLocalInt = DeleteLocal!NWInt; 244 /// 245 alias DeleteLocalFloat = DeleteLocal!NWFloat; 246 /// 247 alias DeleteLocalString = DeleteLocal!NWString; 248 /// 249 alias DeleteLocalObject = DeleteLocal!NWObject; 250 /// 251 alias DeleteLocalLocation = DeleteLocal!NWLocation; 252 253 254 255 /// 256 NWInt GetItemPropertyType(NWItemproperty ip){ 257 return ip.type; 258 } 259 /// 260 NWInt GetItemPropertySubType(NWItemproperty ip){ 261 return ip.subType; 262 } 263 /// 264 NWInt GetItemPropertyCostTableValue(NWItemproperty ip){ 265 return ip.costValue; 266 } 267 /// 268 NWInt GetItemPropertyParam1Value(NWItemproperty ip){ 269 return ip.p1; 270 } 271 272 273 private static nwn.gff.GffStruct[] currentGetItemPropertyRangeGff; 274 private static const(nwn.fastgff.GffStruct)[] currentGetItemPropertyRangeFastGff; 275 276 /// 277 NWItemproperty GetFirstItemProperty(ST)(ref ST oItem) if(isGffStruct!ST) { 278 mixin(ImportGffLib!ST); 279 static if(is(ST: nwn.gff.GffStruct)) 280 alias currentGetItemPropertyRange = currentGetItemPropertyRangeGff; 281 else 282 alias currentGetItemPropertyRange = currentGetItemPropertyRangeFastGff; 283 284 if(auto val = "PropertiesList" in oItem) 285 currentGetItemPropertyRange = val.get!GffList; 286 else 287 return NWInitValue!NWItemproperty; 288 289 if(currentGetItemPropertyRange.empty) 290 return NWInitValue!NWItemproperty; 291 292 return currentGetItemPropertyRange.front.toNWItemproperty; 293 } 294 295 /// 296 NWItemproperty GetNextItemProperty(ST)(ref ST oItem) if(isGffStruct!ST) { 297 mixin(ImportGffLib!ST); 298 static if(is(ST: nwn.gff.GffStruct)) 299 alias currentGetItemPropertyRange = currentGetItemPropertyRangeGff; 300 else 301 alias currentGetItemPropertyRange = currentGetItemPropertyRangeFastGff; 302 303 if(currentGetItemPropertyRange.empty) 304 return NWInitValue!NWItemproperty; 305 306 currentGetItemPropertyRange.popFront(); 307 308 if(currentGetItemPropertyRange.empty) 309 return NWInitValue!NWItemproperty; 310 311 return currentGetItemPropertyRange.front.toNWItemproperty; 312 } 313 314 unittest{ 315 immutable itemData = cast(immutable ubyte[])import("ceinturedeschevaliersquidisentni.uti"); 316 317 foreach(GFF ; AliasSeq!(nwn.gff.Gff, nwn.fastgff.FastGff)){ 318 auto item = new GFF(itemData).root; 319 320 size_t index = 0; 321 auto ip = GetFirstItemProperty(item); 322 while(GetIsItemPropertyValid(ip)){ 323 assert(ip == item["PropertiesList"][index].toNWItemproperty, "Mismatch on ip " ~ index.to!string); 324 index++; 325 ip = GetNextItemProperty(item); 326 } 327 assert(index == 2); 328 } 329 } 330 331 /// 332 NWInt GetBaseItemType(ST)(in ST oItem) if(isGffStruct!ST) { 333 mixin(ImportGffLib!ST); 334 return oItem["BaseItem"].to!NWInt; 335 } 336 337 /// 338 void AddItemProperty(NWInt nDurationType, NWItemproperty ipProperty, ref nwn.gff.GffStruct oItem, NWFloat fDuration=0.0f){ 339 import nwn.gff; 340 assert(nDurationType == DURATION_TYPE_PERMANENT, "Only permanent is supported"); 341 if("PropertiesList" !in oItem){ 342 oItem["PropertiesList"] = GffList(); 343 } 344 345 static nwn.gff.GffStruct buildPropStruct(in NWItemproperty iprp){ 346 nwn.gff.GffStruct ret; 347 with(ret){ 348 assert(iprp.type < getTwoDA("itempropdef").rows); 349 350 ret["PropertyName"] = GffWord(iprp.type); 351 352 immutable subTypeTable = getTwoDA("itempropdef").get("SubTypeResRef", iprp.type); 353 if(subTypeTable is null) 354 assert(iprp.subType==uint16_t.max || iprp.subType == 0, format!"subType=%d is pointing to non-existent SubTypeTable (for iprp=%s)"(iprp.subType, iprp)); 355 else 356 assert(iprp.subType!=uint16_t.max, "iprp.subType must be defined"); 357 358 ret["Subtype"] = GffWord(iprp.subType); 359 360 string costTableResRef = getTwoDA("itempropdef").get("CostTableResRef", iprp.type); 361 if(costTableResRef is null) 362 assert(iprp.costValue==uint16_t.max || iprp.costValue == 0, format!"costValue=%d is pointing to non-existent CostTableResRef (for iprp=%s)"(iprp.costValue, iprp)); 363 else 364 assert(iprp.costValue!=uint16_t.max, "iprp.costValue must be defined"); 365 366 ret["CostTable"] = GffByte(costTableResRef !is null? costTableResRef.to!ubyte : 0); 367 ret["CostValue"] = GffWord(iprp.costValue); 368 369 immutable paramTableResRef = getTwoDA("itempropdef").get("Param1ResRef", iprp.type); 370 if(paramTableResRef !is null){ 371 assert(iprp.p1!=uint8_t.max, "iprp.p1 must be defined for IPRP " ~ iprp.to!string); 372 ret["Param1"] = GffByte(paramTableResRef.to!ubyte); 373 ret["Param1Value"] = GffByte(iprp.p1); 374 } 375 else{ 376 assert(iprp.p1==uint8_t.max || iprp.p1==0, format!"iprp.p1 pointing to non-existent Param1ResRef (for iprp=%s)"(iprp.to!string)); 377 ret["Param1"] = GffByte(0); 378 ret["Param1Value"] = GffByte(0); 379 } 380 381 //ret["Param2"] = GffByte(0); 382 //ret["Param2Value"] = GffByte(0); 383 ret["ChanceAppear"] = GffByte(100); 384 ret["UsesPerDay"] = GffByte(255); 385 ret["Useable"] = GffByte(1); 386 } 387 return ret; 388 } 389 390 oItem["PropertiesList"].get!GffList ~= buildPropStruct(ipProperty); 391 } 392 393 /// 394 void RemoveItemProperty(ref nwn.gff.GffStruct oItem, NWItemproperty ipProperty){ 395 import nwn.gff; 396 import std.algorithm; 397 398 auto subTypeTable = getTwoDA("itempropdef").get("SubTypeResRef", ipProperty.type); 399 bool hasSubType = subTypeTable !is null && subTypeTable != ""; 400 auto costValueTable = getTwoDA("itempropdef").get("CostTableResRef", ipProperty.type); 401 bool hasCostValue = costValueTable !is null && costValueTable != "" && costValueTable.to!int > 0; 402 auto param1Table = getTwoDA("itempropdef").get("Param1ResRef", ipProperty.type); 403 bool hasParam1 = param1Table !is null && param1Table != "" && param1Table.to!int >= 0; 404 405 oItem["PropertiesList"].get!GffList.children = oItem["PropertiesList"].get!GffList.children.remove!((node){ 406 auto ip = node.toNWItemproperty; 407 408 if(ip.type != ipProperty.type) 409 return false; 410 if(hasSubType && ip.subType != ipProperty.subType) 411 return false; 412 if(hasCostValue && ip.costValue != ipProperty.costValue) 413 return false; 414 if(hasParam1 && ip.p1 != ipProperty.p1) 415 return false; 416 return true; 417 }); 418 } 419 420 unittest{ 421 import nwn.gff; 422 immutable itemData = cast(immutable ubyte[])import("ceinturedeschevaliersquidisentni.uti"); 423 auto item = new Gff(itemData).root; 424 425 initTwoDAPaths(["unittest/2da"]); 426 427 auto ipRegen10 = NWItemproperty(51, 0, 10); 428 429 AddItemProperty(DURATION_TYPE_PERMANENT, ipRegen10, item); 430 assert(item["PropertiesList"].get!GffList.length == 3); 431 432 auto addedProp = item["PropertiesList"].get!GffList[$-1]; 433 assert(addedProp["PropertyName"].get!GffWord == 51); 434 assert(addedProp["Subtype"].get!GffWord == 0); 435 assert(addedProp["CostValue"].get!GffWord == 10); 436 assert(addedProp["CostTable"].get!GffByte == 2); 437 assert(addedProp["Param1"].get!GffByte == 0); 438 assert(addedProp["Param1Value"].get!GffByte == 0); 439 //assert(addedProp["Param2"].get!GffByte == 0); 440 //assert(addedProp["Param2Value"].get!GffByte == 0); 441 assert(addedProp["ChanceAppear"].get!GffByte == 100); 442 assert(addedProp["UsesPerDay"].get!GffByte == 255); 443 assert(addedProp["Useable"].get!GffByte == 1); 444 445 auto ipRegen5 = NWItemproperty(51, 0, 5); 446 RemoveItemProperty(item, ipRegen5); 447 assert(item["PropertiesList"].get!GffList.length == 3); 448 449 RemoveItemProperty(item, ipRegen10); 450 assert(item["PropertiesList"].get!GffList.length == 2); 451 } 452 453 /// 454 NWInt GetItemPropertyDurationType(NWItemproperty iprp){ 455 // duration is not stored in the struct 456 return DURATION_TYPE_PERMANENT; 457 } 458 459 /// 460 NWInt GetItemPropertyCostTable(NWItemproperty iProp){ 461 return getTwoDA("itempropdef").get!NWInt("CostTableResRef", iProp.type, -1); 462 } 463 464 465 466 /// 467 NWString Get2DAString(NWString s2DA, NWString sColumn, NWInt nRow){ 468 return getTwoDA(s2DA).get!NWString(sColumn, nRow, ""); 469 } 470 471 472 /// 473 NWString IntToString(NWInt nInteger){ 474 return nInteger.to!NWString; 475 } 476 /// 477 NWFloat IntToFloat(NWInt nInteger){ 478 return nInteger.to!NWFloat; 479 } 480 /// 481 NWInt FloatToInt(NWFloat fFloat){ 482 return fFloat.to!NWInt; 483 } 484 /// 485 NWString FloatToString(NWFloat fFloat, NWInt nWidth=18, NWInt nDecimals=9){ 486 return format("%" ~ nWidth.to!string ~ "." ~ nDecimals.to!string ~ "f", fFloat); 487 } 488 /// 489 NWInt StringToInt(NWString sNumber){ 490 return sNumber.to!NWInt; 491 } 492 /// 493 NWFloat StringToFloat(NWString sNumber){ 494 return sNumber.to!NWFloat; 495 }