Menu

Show posts

This section allows you to view all posts made by this member. Note that you can only see posts made in areas you currently have access to.

Show posts Menu

Messages - monkey0506

#441
Quote from: Crimson Wizard on Fri 26/09/2014 22:32:21Actually it does create a separate raw data with the call to CompileGameToDTAFile.
What it needs, is a change in a workflow that would optionally leave game data unmerged with any exe.

I kind of realized this in retrospect... I lost sight of what I was actually trying to accomplish I think (obviously, I'm trying to separate building the EXE from building the actual game data file). I realized what the real root of my problem was though. The engine file operations only have file modes for "Open", "Create", and "CreateAlways". If the file mode is "Create" then confusingly (IMO) it actually does an Append operation:

Common/util/filestream.cpp:190-200
Code: cpp
    else if (open_mode == kFile_Create)
    {
        if (work_mode == kFile_Write)
        {
            mode.AppendChar('a');
        }
        else if (work_mode == kFile_Read || work_mode == kFile_ReadWrite)
        {
            mode.Append("a+");
        }
    }


I was treating this as "OpenOrCreate" which opens the file at the beginning. And apparently I never did actually check the Length of the stream (in my C# implementation), just the Position...

Edit: With one more minor correction (writing startOffset as a 64-bit long instead of 32-bit int), I can now confirm that my C# conversion of "make_data_file" is fully functional. Now I'll start working on actually splitting the game data out from the EXE during the build process, and appending them back together afterwards. ;)
#442
I'm having a bit of a problem tracking down why my code to build the game's data file from C# is failing to produce a valid EXE. I've combed over the native C++ implementation and compared it to my C# implementation dozens of times, and I'm not seeing where the problem lies. What I have been able to determine is that the native code is (apparently) writing data to the EXE file well ahead of my current implementation, which is getting things massively out of order, and causing some data to not be copied at all. This may be a bit of a code dump, but any help sorting this out is appreciated.

When creating the EXE, the Editor calls upon the native code to build the data file.

Editor/AGS.Editor/NativeProxy.cs:388-391
Code: csharp
        public void CreateGameEXE(string[] fileList, Game game, string baseFileName)
        {
            _native.CreateDataFile(fileList, game.Settings.SplitResources * 1000000, baseFileName, true);
        }


Editor/AGS.Native/ScriptCompiler.cpp:116-139
Code: cpp
		void NativeMethods::CreateDataFile(cli::array<String^> ^fileList, long splitSize, String ^baseFileName, bool isGameEXE)
		{
			char **fileNames = (char**)malloc(sizeof(char*) * fileList->Length);
			for (int i = 0; i < fileList->Length; i++)
			{
				fileNames[i] = (char*)malloc(fileList[i]->Length + 1);
				ConvertFileNameToCharArray(fileList[i], fileNames[i]);
			}
			char baseFileNameChars[MAX_PATH];
			ConvertFileNameToCharArray(baseFileName, baseFileNameChars);

			const char *errorMsg = make_data_file(fileList->Length, fileNames, splitSize, baseFileNameChars, isGameEXE);

			for (int i = 0; i < fileList->Length; i++)
			{
				free(fileNames[i]);
			}
			free(fileNames);

			if (errorMsg != NULL)
			{
				throw gcnew AGSEditorException(gcnew String(errorMsg));
			}
		}


In particular, I noticed that all this is doing is forwarding the parameters to the unmanaged method, make_data_file (rather long, over 100 lines).

Editor/AGS.Native/agsnative.cpp:2448-2619
Spoiler
Code: cpp
const char* make_data_file(int numFiles, char * const*fileNames, long splitSize, const char *baseFileName, bool makeFileNameAssumptionsForEXE)
{
  int a,b;
  Stream*wout;
  char tomake[MAX_PATH];
  ourlib.num_data_files = 0;
  ourlib.num_files = numFiles;
  Common::AssetManager::SetSearchPriority(Common::kAssetPriorityDir);

  int currentDataFile = 0;
  long sizeSoFar = 0;
  bool doSplitting = false;

  for (a = 0; a < numFiles; a++)
  {
	  if (splitSize > 0)
	  {
		  if (stricmp(fileNames[a], sprsetname) == 0) 
		  {
			  // the sprite file's appearance signifies it's time to start splitting
			  doSplitting = true;
			  currentDataFile++;
			  sizeSoFar = 0;
		  }
		  else if ((sizeSoFar > splitSize) && (doSplitting) && 
			  (currentDataFile < MAXMULTIFILES - 1))
		  {
			  currentDataFile++;
			  sizeSoFar = 0;
		  }
	  }
	  long thisFileSize = 0;
	  Stream *tf = Common::File::OpenFileRead(fileNames[a]);
	  thisFileSize = tf->GetLength();
	  delete tf;
	  
	  sizeSoFar += thisFileSize;

    const char *fileNameSrc = fileNames[a];

  	if (strrchr(fileNames[a], '\\') != NULL)
		  fileNameSrc = strrchr(fileNames[a], '\\') + 1;
	  else if (strrchr(fileNames[a], '/') != NULL)
		  fileNameSrc = strrchr(fileNames[a], '/') + 1;

    if (strlen(fileNameSrc) >= MAX_FILENAME_LENGTH)
    {
      char buffer[500];
      sprintf(buffer, "Filename too long: %s", fileNames[a]);
      ThrowManagedException(buffer);
    }
		strcpy(ourlib.filenames[a], fileNameSrc);

	  ourlib.file_datafile[a] = currentDataFile;
	  ourlib.length[a] = thisFileSize;
  }

  ourlib.num_data_files = currentDataFile + 1;

  long startOffset = 0;
  long mainHeaderOffset = 0;
  char outputFileName[MAX_PATH];
  char firstDataFileFullPath[MAX_PATH];

  if (makeFileNameAssumptionsForEXE)
  {
	  _mkdir("Compiled");
  }

  // First, set up the ourlib.data_filenames array with all the filenames
  // so that write_clib_header will write the correct amount of data
  for (a = 0; a < ourlib.num_data_files; a++) 
  {
	  if (makeFileNameAssumptionsForEXE) 
	  {
		  sprintf(ourlib.data_filenames[a], "%s.%03d", baseFileName, a);
		  if (a == 0)
		  {
			  strcpy(&ourlib.data_filenames[a][strlen(ourlib.data_filenames[a]) - 3], "exe");
		  }
	  }
	  else 
	  {
    	if (strrchr(baseFileName, '\\') != NULL)
		    strcpy(ourlib.data_filenames[a], strrchr(baseFileName, '\\') + 1);
	    else if (strrchr(baseFileName, '/') != NULL)
		    strcpy(ourlib.data_filenames[a], strrchr(baseFileName, '/') + 1);
	    else
		    strcpy(ourlib.data_filenames[a], baseFileName);
	  }
  }

  // adjust the file paths if necessary, so that write_clib_header will
  // write the correct amount of data
  for (b = 0; b < ourlib.num_files; b++) 
  {
	Stream *iii = find_file_in_path(tomake, ourlib.filenames[b]);
	if (iii != NULL)
	{
		delete iii;

		if (!makeFileNameAssumptionsForEXE)
		  strcpy(ourlib.filenames[b], tomake);
	}
  }

  // now, create the actual files
  for (a = 0; a < ourlib.num_data_files; a++) 
  {
	  if (makeFileNameAssumptionsForEXE) 
	  {
		  sprintf(outputFileName, "Compiled\\%s", ourlib.data_filenames[a]);
	  }
	  else 
	  {
		  strcpy(outputFileName, baseFileName);
      }
      if (a == 0) strcpy(firstDataFileFullPath, outputFileName);

	  wout = Common::File::OpenFile(outputFileName,
          (a == 0) ? Common::kFile_Create : Common::kFile_CreateAlways, Common::kFile_Write);
	  if (wout == NULL) 
	  {
		  return "ERROR: unable to open file for writing";
	  }

	  startOffset = wout->GetLength();
    wout->Write("CLIB\x1a",5);
    wout->WriteByte(21);  // version
    wout->WriteByte(a);   // file number

    if (a == 0) 
	{
      mainHeaderOffset = wout->GetPosition();
      write_clib_header(wout);
    }

    for (b=0;b<ourlib.num_files;b++) {
      if (ourlib.file_datafile[b] == a) {
        ourlib.offset[b] = wout->GetPosition() - startOffset;

		Stream *iii = find_file_in_path(NULL, ourlib.filenames[b]);
        if (iii == NULL) {
          delete wout;
          unlink(outputFileName);

		  char buffer[500];
		  sprintf(buffer, "Unable to find file '%s' for compilation. Do not remove files during the compilation process.", ourlib.filenames[b]);
		  ThrowManagedException(buffer);
        }

        if (copy_file_across(iii,wout,ourlib.length[b]) < 1) {
          delete iii;
          return "Error writing file: possibly disk full";
        }
        delete iii;
      }
    }
	if (startOffset > 0)
	{
		wout->WriteInt32(startOffset);
		wout->Write(clibendsig, 12);
	}
    delete wout;
  }

  wout = Common::File::OpenFile(firstDataFileFullPath, Common::kFile_Open, Common::kFile_ReadWrite);
  wout->Seek(Common::kSeekBegin, mainHeaderOffset);
  write_clib_header(wout);
  delete wout;
  return NULL;
}
[close]

As far as I can tell, this doesn't even open the EXE file for writing until it is near the end of the function.

Editor/AGS.Native/agsnative.cpp:2554-2583
Code: cpp
  // now, create the actual files
  for (a = 0; a < ourlib.num_data_files; a++) 
  {
	  if (makeFileNameAssumptionsForEXE) 
	  {
		  sprintf(outputFileName, "Compiled\\%s", ourlib.data_filenames[a]);
	  }
	  else 
	  {
		  strcpy(outputFileName, baseFileName);
      }
      if (a == 0) strcpy(firstDataFileFullPath, outputFileName);

	  wout = Common::File::OpenFile(outputFileName,
          (a == 0) ? Common::kFile_Create : Common::kFile_CreateAlways, Common::kFile_Write);
	  if (wout == NULL) 
	  {
		  return "ERROR: unable to open file for writing";
	  }

	  startOffset = wout->GetLength();
    wout->Write("CLIB\x1a",5);
    wout->WriteByte(21);  // version
    wout->WriteByte(a);   // file number

    if (a == 0) 
	{
      mainHeaderOffset = wout->GetPosition();
      write_clib_header(wout);
    }
    // ...


Here we see that as soon as the file is opened, it is recording the file's length (line 21 in the above snippet, line 2574 in the file), writing a bit of data (the next three lines), and then if it is the EXE, recording the file's current position. If the file were being newly created then this wouldn't make sense, but as seen on line 15 of the snippet (2568) the EXE is being opened if it already exists. From my tests, it seems that the EXE is being created elsewhere first and having some 2178048 bytes of data written into it (as per the Length and Position of the Stream) before make_data_file is being called.

However, if I replace the call in NativeProxy.cs with a call to my C# implementation of make_data_file, the EXE is not being created first.

My question, if anyone can be bothered to read through all of this, is at what point is the EXE being created and written to when using the native implementation that isn't being invoked with my C# implementation? I'm at a total loss as to where this file is being created (and even when the necessary code is being invoked).

My C# implementation is up in my personal fork, if anyone wants to look through it.
#443
It's great to hear that the latest APK is working well. I'll pass that along to Himalaya so they can try it out! :-D
#444
Quote from: Crimson Wizard on Wed 24/09/2014 18:28:34* Building game for multiple platforms;

On that note, I have partially refactored building the data file out of the native code. I'll need to work on it some more (hopefully this weekend) and then I can incorporate it into the Linux build process which I've already streamlined into the editor code. It's currently not building correctly, though most of the data seems to be getting copied. Just need to review it and figure out what I've mucked up.
#445
My first thought is that you could probably get away with a simple extender to unpause the game, say the text, then pause the game again...

Code: ags
void SayWhilePaused(this Character*, String text)
{
  bool paused = IsGamePaused();
  if (paused) UnPauseGame();
  this.Say(text);
  if (paused) PauseGame();
}


As for a timer that runs independently of whether the game is paused, this is why I discourage anyone from using the built-in SetTimer function -- ever. All you need is a global variable. Granted, you do have to update it yourself, but to set it, you just set the value of the variable. Then in repeatedly_execute you can do:

Code: ags
  if (timer) timer--;


This also allows you to check IsGamePaused or update it in repeatedly_execute_always for timers that run during blocking events. There's a number of ways to approach the problem, but the extender is probably the simplest.
#446
I use Google Drive for schoolwork all the time. Its "Sheets" app is very similar to the free online version of MS Excel (which... is also free and web-based). If you don't mind something that is "almost but not quite entirely unlike" MS Excel, you could use OpenOffice. You can also try out LibreOffice, which is bundled with the version of Linux I use for porting Steam games (Linux Mint), but I have no personal experience with it.

My recommendation is Google Drive, unless you absolutely need to be sure it's compatible with MS Excel, then just use Microsoft Office Live (Google Sheets can import/export Excel spreadsheets, but going back and forth repeatedly can produce inconsistencies).
#447
General Discussion / Re: Free Steam keys!
Sun 21/09/2014 22:26:51
Hey Adeel, I sent you a PM but I don't think you saw it. I need your email or Steam ID, I don't actually have the key directly, I'll send it as a gift through Steam.
#448
If you want to do this generically (e.g., do something when any dialog ends, like turn a GUI back on), you can combine Khris' suggestion with the custom dialog rendering options. Basically you can tell that a dialog has started by setting a variable in dialog_options_get_dimensions or dialog_options_render and then tell that a dialog has ended when repeatedly_execute is triggered.

Seems like you got it sorted out, but just wanted to mention an alternative that lets you know when any dialog has ended without having to modify every dialog script.
#449
This sounds like an appropriate direction to me. At the very minimum the public stable releases will have their tertiary version number increased (including for hotfixes and updates), and the fourth version number will be reserved for WIP releases. The "generation" and "major" version numbers will continue to be used as they historically have been. Yeah? 8-)
#450
I still maintain that the four-point version system works well. I agree with the suggestion to start resetting it though.

And that's about all that I can usefully say on that for now. :P
#451
As for why it happens, I imagine it probably had something to do with the way interactions were handled in legacy versions of the editor. It's been a long time since I've used even AGS 2.72 (though I know some people still swear by it, perhaps they could chime in), but I believe the Interaction Editor was actually set up in a similar fashion such that any click, handled or not, was still a part of the "Any click" event. I think this was done in part to help simplify the graphical "scripting" (mediated through the Interaction Editor) and make it more user-friendly for beginners.

Edit: I've rethought this again and I think that the introduction of IsInteractionAvailable methods on a per-entity basis (e.g., Character.IsInteractionAvailable, Hotspot.IsInteractionAvailable...) really covers everything you would need. The 3.3.1 betas introduce these methods, so you could simply check at the beginning of your AnyClick events:

Code: ags
function hBlue_AnyClick()
{
  if (hBlue.IsInteractionAvailable(game.used_mode)) return;
  // ...
}


I think this is the best approach for now, without invalidating or breaking existing scripts.
#452
I've been looking to rebuild the native libraries (alfont_mt.lib, alleg_s_crt.lib, ddraw.lib, etc.). I'm just curious, does anyone have an exhaustive list of what is needed to rebuild the libraries?
#453
This is confirmed behavior of the current versions of AGS (per testing and a review of the engine source). I wasn't sure if this was intended behavior, but it's existed since at least the original source code import from AGS 3.2.1.

Current source:
Engine/ac/global_hotspot.cpp:117-122
Code: cpp
    if (thisroom.hotspotScripts != NULL) // there are AGS 3.0+ style interaction scripts for the room
    {
        if (passon>=0) // cursor is a standard mode
            run_interaction_script(thisroom.hotspotScripts[hotspothere], passon, 5, (passon == 3)); // invoke hotspot interaction event (e.g., hBlue_Interact)
        run_interaction_script(thisroom.hotspotScripts[hotspothere], 5);  // any click on hotspot -- (e.g., hBlue_AnyClick) this is ALWAYS invoked
    }


Legacy source:
Engine/ac.cpp:16620-16625
Code: cpp
  if (thisroom.hotspotScripts != NULL) 
  {
    if (passon>=0)
      run_interaction_script(thisroom.hotspotScripts[hotspothere], passon, 5, (passon == 3));
    run_interaction_script(thisroom.hotspotScripts[hotspothere], 5);  // any click on hotspot
  }


As you can see, the code is exactly identical. Whether or not this qualifies as a bug could be up in the air, but as it's been so long standing it's probably safer to assume that it was intentional behavior.

To be absolutely certain that your AnyClick event is unique, you would have to do something like this:

Code: ags
function hBlue_AnyClick()
{
  if ((game.used_mode == eModeWalk) || (game.used_mode == eModeLook) || (game.used_mode == eModeInteract) ||
      (game.used_mode == eModeUseinv) || (game.used_mode == eModeTalk) || (game.used_mode == eModePickup) ||
      (game.used_mode == eModeUserMode8) || (game.used_mode == eModeUserMode9))
  {
    // cursor mode was not unique, abort
    return;
  }
  if (oCup.Visible == false)
  {
    Display("Anyclick has been used.");
    oCup.Visible = true;
  }
}


I would suggest using IsInteractionAvailable except that if there's anything in front of the hotspot you might get unreliable results (though it does return false if only the AnyClick event is defined). This also brings to my attention that the engine is defining a Hotspot.IsInteractionAvailable method, but it's not imported and since there's a global function by the same name it can't be pulled in by user scripts either... :~( (Sorry, that's something being included in 3.3.1 and I didn't have a build of the most recent source)
#454
Quote from: Crimson Wizard on Fri 19/09/2014 20:17:11I mean, we will have to explain why the previous version was 3.4 and new one suddenly 3.6. Your examples are both low-level developer tools, and AGS is a common user product.

I agree with this. I think that in general the way the versioning is controlled now is appropriate. The way it's worked thus far, the first version number has actually corresponded exactly to major editor revisions (e.g., complete IDE rewrite). The secondary version number is generally increased when there are major changes to the editor or engine (script API changes, major bugfixes, etc.). The tertiary version number is then reserved for more minor changes, minor bugfixes, minor feature fixes, and the like. This all applies directly to the public release version numbers, and I think it works very well. The fourth version number being used to differentiate between development revisions (using even numbers for stable releases) also serves its purpose.

That being said, it may not be immediately obvious how the versioning works. Is this actually documented explicitly anywhere?
#455
General Discussion / Re: Free Steam keys!
Fri 19/09/2014 20:14:19
Arr, mateys. There's booty to be had, if ye are ready fer the adventure of yer filthy lives. I have in me possession a key which unlocks a glorious treasure -- The Secret of Monkey Island: Special Edition and Monkey Island 2: LeChuck's Revenge: Special Edition! In honor of Ye Olde International Talk Like A Pirate Day, this loot can be yours! Act now, don't delay! And remember, 'X' marks the spot!

P.S. If anybody happens to have a spare key to the Super Smash 3DS demo...(roll) (Noticed last night that the demo is public now, so that's that)

P.S.S. Both games are a single Steam key, it's for the bundle.
#456
Could you post your on_mouse_click function? Also if you're calling these functions yourself from anywhere, that code would be useful.
#457
Quote from: Cassiebsg on Thu 18/09/2014 20:34:20I do wonder that you gave the option of 0$ in the previous question, but overlook that possibly in the next question. ;)

I understand it could be useful to have a distinction for "I only play free games", but the option of "[I] Never [buy games]" is hardly overlooking spending $0/yr. ;)

Also, seems I misunderstood the teacher on when we are checking the results. That will be on this coming Tuesday, so if you haven't taken it yet please do! :)
#458
Adeel, I don't believe you were in the first 100 responses. SurveyMonkey only displays those results on my free account. The overall number is still shown though, so that's what our class will take into account this afternoon.

I know posting the survey here may have skewed my results, but I'll see how it ranks up against others as well, which should give us an idea of how extreme the bias is.
#459
Quote from: Stupot+ on Wed 17/09/2014 13:41:43Done. Do you want us to share it on Facebook and stuff for more results?

If you don't mind, that would be awesome!

Quote from: Intense Degree on Wed 17/09/2014 16:55:33I only wish some of the answers could have been higher!

Which questions, specifically? Hours spent playing? Because I doubt anyone actually spends over $9000 every year on gaming. :P

Also, if you're interested you can view the responses from the first 100 people surveyed* (sorry, I can't afford a premium account!): here.

*But still, for the purposes of the class, the more people that actually take the survey, the better.
#460
Quote from: Mandle on Wed 17/09/2014 02:38:26Done! One thing though, there is no option for "I mostly/only play free games" in the question about when/how you buy games.

That honestly hadn't occurred to me. I tried to make the survey as broad as possible, while still geared toward "gamers". I'll make note of the feedback to the class as well.

I greatly appreciate the feedback from everyone. We have somewhat of a competition going in the class, so every response is appreciated! :=
SMF spam blocked by CleanTalk