Example #1
0
 public TalkScript GetTalkScript(SmallMapReferences.SingleMapReference.SmallMapMasterFiles smallMapRef, int nNPC)
 {
     if (NonPlayerCharacterReference.IsSpecialDialogType((NonPlayerCharacterReference.NPCDialogTypeEnum)nNPC))
     {
         return(null);
     }
     return(_talkScriptRefs[smallMapRef][nNPC]);
 }
Example #2
0
        /// <summary>
        /// Initlializes the talk scripts into a fairly raw byte[] format
        /// </summary>
        /// <param name="u5Directory">directory of Ultima 5 data files</param>
        /// <param name="mapMaster">the small map reference (helps pick *.tlk file)</param>
        private void InitializeTalkScriptsRaw(string u5Directory, SmallMapReferences.SingleMapReference.SmallMapMasterFiles mapMaster)
        {
            // example NPC 1 at Castle in Lord British's castle
            // C1 EC  E9 F3 F4 E1 E9 F2 01 C2 E1 F2 E4 00
            // 65 108  69

            string talkFilename = Path.Combine(u5Directory, SmallMapReferences.SingleMapReference.GetTlkFilenameFromMasterFile(mapMaster));

            // the raw bytes of the talk file
            List <byte> talkByteList = Utils.GetFileAsByteList(talkFilename);

            // need this to make sure we don't fall of the end of the file when we read it
            FileInfo fi           = new FileInfo(talkFilename);
            long     talkFileSize = fi.Length;

            // keep track of the NPC to file offset mappings
            //List<NPC_TalkOffset> npcOffsets;

            // the first word in the talk file tells you how many characters are referenced in script
            int nEntries = Utils.LittleEndianConversion(talkByteList[0], talkByteList[1]);

            //talkRefs.Add(mapMaster, new List<byte[]>(nEntries));
            _talkRefs.Add(mapMaster, new Dictionary <int, byte[]>(nEntries));

            // a list of all the offsets
            //npcOffsets = new List<NPC_TalkOffset>(nEntries);
            Dictionary <int, NPCTalkOffset> npcOffsets = new Dictionary <int, NPCTalkOffset>(nEntries);

            unsafe
            {
                // you are in a single file right now
                for (int i = 0; i < (nEntries * sizeof(NPCTalkOffset)); i += sizeof(NPCTalkOffset))
                {
                    // add 2 because we know we are starting at an offset
                    unsafe {
                        NPCTalkOffset talkOffset = (NPCTalkOffset)Utils.ReadStruct(talkByteList, 2 + i, typeof(NPCTalkOffset));
                        npcOffsets[talkOffset.npcIndex] = talkOffset;

                        // OMG I'm tired.. figure out why this isn't printing properly....
                        if (_bIsDebug)
                        {
                            Console.WriteLine(@"NPC #" + npcOffsets[talkOffset.npcIndex].npcIndex + @" at offset " + npcOffsets[talkOffset.npcIndex].fileOffset + @" in file " + talkFilename);
                        }
                    }
                }
                // you are in a single file right now
                // repeat for every single NPC in the file
                int count = 1;
                foreach (int key in npcOffsets.Keys)
                {
                    long chunkLength = 0; // didn't want a long, but the file size is long...

                    // calculate the offset size
//                    foreach (int key in npcOffsets.Keys)
                    {
                        if (count < npcOffsets.Keys.Count)
                        {
                            chunkLength = npcOffsets[key + 1].fileOffset - npcOffsets[key].fileOffset;
                        }
                        else
                        {
                            chunkLength = talkFileSize - npcOffsets[key].fileOffset;
                        }

                        count++;
                    }

                    byte[] chunk = new byte[chunkLength];

                    // copy only the bytes from the offset
                    talkByteList.CopyTo(npcOffsets[key].fileOffset, chunk, 0, (int)chunkLength);
                    // Add the raw bytes to the specific Map+NPC#
                    _talkRefs[mapMaster].Add(key, chunk); // have to make an assumption that the values increase 1 at a time, this should be true though
                }
            }
        }
Example #3
0
        /// <summary>
        /// Intializes an individual TalkingScript using the raw data created from InitializeTalkScriptsRaw
        /// <remark>May God have mercy on my soul if I ever need to debug or troubleshoot this again.</remark>
        /// </summary>
        /// <param name="smallMapRef">the small map reference</param>
        /// <param name="index">NPC Index</param>
        private TalkScript InitializeTalkScriptFromRaw(SmallMapReferences.SingleMapReference.SmallMapMasterFiles smallMapRef, int index)
        {
            TalkScript talkScript = new TalkScript();                                   // the script we are building and will return

            List <bool> labelsSeenList = new List <bool>(TalkScript.TOTAL_LABELS);      // keeps track of the labels we have already seen

            labelsSeenList.AddRange(Enumerable.Repeat(false, TalkScript.TOTAL_LABELS)); // creates a list of "false" bools to set the default labelsSeenList

            bool   writingSingleCharacters = false;                                     // are we currently writing a single character at a time?
            string buildAWord = string.Empty;                                           // the word we are currently building if we are writingSingleCharacters=true

            int nGoldCharsLeft = 0;

            foreach (byte byteWord in _talkRefs[smallMapRef][index])
            {
                // if a NULL byte is provided then you need to go the next line, resetting the writingSingleCharacters so that a space is not inserted next line
                // bh: Sept 21, 2019 - had to add a disgusting hack to account for what appears to be broken
                //   data or a misunderstood algorithm - they seem to have an end of script line in the middle of a response
                //   there could be a rule I simply don't undertstand, but for now - i hack
                if (byteWord == END_OF_SCRIPTLINE_BYTE && !buildAWord.EndsWith("to give unto charity!"))
                {
                    buildAWord += "\n";
                    // we are done with the entire line, so lets add it the script
                    talkScript.AddTalkCommand(TalkScript.TalkCommand.PlainString, buildAWord);
                    // tells the script to move onto the next command
                    talkScript.NextLine();
                    // reset some vars
                    buildAWord = string.Empty;
                    writingSingleCharacters = false;
                    continue;
                }

                byte tempByte          = (byte)((int)byteWord); // this is the byte that we will manipulate, leaving the byteWord in tact
                bool usePhraseLookup   = false;                 // did we do a phrase lookup (else we are typing single letters)
                bool useCompressedWord = false;                 // did we successfully use a compressed word?

                // if it's one of the bytes that requires a subtraction of 0x80 (128)
                if (byteWord >= 165 && byteWord <= 218)
                {
                    tempByte -= TALK_OFFSET_ADJUST;
                }
                else if (byteWord >= 225 && byteWord <= 250)
                {
                    tempByte -= TALK_OFFSET_ADJUST;
                }
                else if (byteWord >= 160 && byteWord <= 161)
                {
                    tempByte -= TALK_OFFSET_ADJUST;
                }
                else
                {
                    // it didn't match which means that it's one the special phrases and we will perform a lookup
                    usePhraseLookup = true;
                }

                // Debug code to help track down particular codes being written
                // if (tempByte == 119) { Console.Write("");  }
                // it wasn't a special phrase which means that the words are being typed one word at a time
                if (!usePhraseLookup)
                {
                    // I'm writing single characters, we will keep track so that when we hit the end we can insert a space
                    writingSingleCharacters = true;
                    // this signifies the end of the printing (sample code enters a newline)
                    if ((char)tempByte == '@')
                    {
                        //Console.WriteLine("");
                        continue;
                    }
                    //Console.Write((char)tempByte);
                    buildAWord += (char)tempByte;
                    if (nGoldCharsLeft > 0 && --nGoldCharsLeft == 0)
                    {
                        talkScript.AddTalkCommand(TalkScript.TalkCommand.PlainString, buildAWord);
                        buildAWord = string.Empty;
                    }
                    // Debug code to help track down an NPCs question or response
                    //if (buildAWord.Contains("to give unto charity")) { Console.Write(""); }
                }
                else // usePhraseLookup = true
                {
                    // We were instructed to perform a lookup, either a compressed word lookup, or a special character

                    // if we were previously writing single characters, but have moved onto lookups, then we add a space, and reset it
                    if (writingSingleCharacters)
                    {
                        writingSingleCharacters = false;
                        buildAWord += " ";
                    }

                    // we are going to lookup the word in the compressed word list, if we throw an exception then we know it wasn't in the list
                    if (_compressedWordRef.IsTalkingWord((int)tempByte))
                    {
                        string talkingWord = _compressedWordRef.GetTalkingWord((int)tempByte);
                        useCompressedWord = true;
                        buildAWord       += talkingWord;
                    }
                    // this is a bit lazy, but if I ask for a string that is not captured in the lookup map, then we know it's a special case
                    else
                    {
                        // oddly enough - we add an existing plain string that we have been building
                        // at the very last second
                        if (buildAWord != string.Empty)
                        {
                            talkScript.AddTalkCommand(TalkScript.TalkCommand.PlainString, buildAWord);
                            buildAWord = string.Empty;
                        }

                        // if the tempByte is within these boundaries then it is a label
                        // it is up to us to determine if it a Label or a GotoLabel
                        if (tempByte >= TalkScript.MIN_LABEL && tempByte <= TalkScript.MAX_LABEL)
                        {
                            // create an offset starting at 0 for label numbering
                            int offset = tempByte - TalkScript.MIN_LABEL;
                            // have I already seen a label once? Then the next label is a definition
                            if (labelsSeenList[offset] == true)
                            {
                                talkScript.AddTalkLabel(TalkScript.TalkCommand.DefineLabel, offset);
                            }
                            else
                            {
                                // the first time you see the label is a goto statement
                                talkScript.AddTalkLabel(TalkScript.TalkCommand.DefineLabel, offset);
                                //talkScript.AddTalkLabel(TalkScript.TalkCommand.GotoLabel, offset);
                                labelsSeenList[offset] = true;
                            }
                        }
                        else
                        {
                            // this is just a standard special command, so let's add it
                            talkScript.AddTalkCommand((TalkScript.TalkCommand)tempByte, string.Empty);
                            if ((TalkScript.TalkCommand)tempByte == TalkScript.TalkCommand.Gold)
                            {
                                // here are always three extra characters after the gold, but only for gold
                                // so we make sure we only capture the next 3 characters
                                nGoldCharsLeft = 3;
                            }
                        }
                    }
                    // we just used a compressed word which means we need to insert a space afterwards
                    if (useCompressedWord)
                    {
                        buildAWord += " ";
                    }
                }
            }
            talkScript.InitScript();
            return(talkScript);
        }
Example #4
0
        // left over structure

        /* [StructLayout(LayoutKind.Sequential, Pack = 1)]
         *      private unsafe struct NPC_Info
         *      {
         *          NPC_Schedule schedule[32];
         *          fixed byte type[32]; // merchant, guard, etc.
         *          fixed byte dialog_number[32];
         *      };*/
        #endregion

        #region Initialization and Constructor routines
        /// <summary>
        /// Initialize NPCs from a particular small map master file set
        /// </summary>
        /// <param name="u5Directory">Directory with Ultima 5</param>
        /// <param name="mapMaster">The master map from which to load</param>
        /// <param name="smallMapRef">Small map reference to help link NPCs to a map</param>
        private void InitializeNPCs(string u5Directory, SmallMapReferences.SingleMapReference.SmallMapMasterFiles mapMaster, SmallMapReferences smallMapRef,
                                    TalkScripts talkScriptsRef, GameState gameStateRef)
        {
            // open the appropriate NPC data file
            string dataFilenameAndPath = Path.Combine(u5Directory, SmallMapReferences.SingleMapReference.GetNPCFilenameFromMasterFile(mapMaster));

            // load the file into memory
            List <byte> npcData = Utils.GetFileAsByteList(dataFilenameAndPath);

            for (int nTown = 0; nTown < TOWNS_PER_NPCFILE; nTown++)
            {
                // fresh collections for each major loop to guarantee they are clean
                List <NonPlayerCharacterReference.NPC_Schedule> schedules = new List <NonPlayerCharacterReference.NPC_Schedule>(NPCS_PER_TOWN);
                List <byte> npcTypes        = new List <byte>(NPCS_PER_TOWN);
                List <byte> npcDialogNumber = new List <byte>(NPCS_PER_TOWN);

                SmallMapReferences.SingleMapReference.Location location     = smallMapRef.GetLocationByIndex(mapMaster, nTown);
                SmallMapReferences.SingleMapReference          singleMapRef = smallMapRef.GetSingleMapByLocation(location, 0);

                //sing = SmallMapRef.GetSingleMapByLocation(SmallMapRef.GetLocationByIndex(mapMaster, nTown);

                int townOffset = (TOWN_OFFSET_SIZE * nTown);

                // bajh: I know this could be done in a single loop, but it would be so damn ugly that I honestly don't even want to both
                // read through the schedules first
                int count = 0;
                // start at the town offset, incremenet by an NPC record each time, for 32 loops
                for (int offset = townOffset; count < NPCS_PER_TOWN; offset += SCHEDULE_OFFSET_SIZE, count++)
                {
                    NonPlayerCharacterReference.NPC_Schedule sched = (NonPlayerCharacterReference.NPC_Schedule)Utils.ReadStruct(npcData, offset, typeof(NonPlayerCharacterReference.NPC_Schedule));
                    schedules.Add(sched);
                }
                // bajh: just shoot me if I ever have to write this again - why on earth did LB write all of his data in different formats!
                // these are single byte, so we can capture them just by jumping to their offsets
                count = 0;
                for (int offset = townOffset; count < NPCS_PER_TOWN; offset++, count++)
                {
                    // add NPC type
                    npcTypes.Add(npcData[offset + STARTING_NPC_TYPE_TOWN_OFFSET]);

                    // add NPC dialog #
                    npcDialogNumber.Add(npcData[offset + STARTING_NPC_DIALOG_TOWN_OFFSET]);
                }

                List <byte> keySpriteList = gameStateRef.NonPlayerCharacterKeySprites.GetAsByteList();

                // go over all of the NPCs, create them and add them to the collection
                for (int nNpc = 0; nNpc < NPCS_PER_TOWN; nNpc++)
                {
                    NonPlayerCharacterReference npc = new NonPlayerCharacterReference(location, gameStateRef, schedules[nNpc], npcTypes[nNpc],
                                                                                      npcDialogNumber[nNpc], nNpc, talkScriptsRef.GetTalkScript(mapMaster, npcDialogNumber[nNpc]), (int)(keySpriteList[nNpc] + 100));
                    npcs.Add(npc);
                    // we also create a quick lookup table by location but first need to check that there is an initialized list inside
                    if (!locationToNPCsDictionary.ContainsKey(singleMapRef.MapLocation))
                    {
                        locationToNPCsDictionary.Add(singleMapRef.MapLocation, new List <NonPlayerCharacterReference>());
                    }
                    locationToNPCsDictionary[singleMapRef.MapLocation].Add(npc);
                }
            }
        }