IJInventoryWindowController.m 34 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079
  1. //
  2. // IJInventoryWindowController.m
  3. // InsideJob
  4. //
  5. // Created by Adam Preble on 10/7/10.
  6. // Copyright 2010 Adam Preble. All rights reserved.
  7. // Changes for SMP World (external player support) ©2011 Manoel Trapier
  8. // Changes for searching Named SSP world and GUI changes ©2011 Nickloose
  9. //
  10. #import "IJInventoryWindowController.h"
  11. #import "IJMinecraftLevel.h"
  12. #import "IJInventoryItem.h"
  13. #import "IJInventoryView.h"
  14. #import "IJItemPropertiesViewController.h"
  15. #import "MAAttachedWindow.h"
  16. #import "NSFileManager+DirectoryLocations.h"
  17. @interface IJInventoryWindowController ()
  18. - (void)saveWorld;
  19. - (void)loadWorldAtIndex:(int)worldIndex;
  20. - (BOOL)isDocumentEdited;
  21. - (void)loadWorldAtFolder:(NSString *)worldFolder;
  22. - (void)loadWorldSelectionControl;
  23. - (void)loadPlayerSelectionControl;
  24. @end
  25. @implementation IJInventoryWindowController
  26. @synthesize playerSelectionControl;
  27. @synthesize worldSelectionControl;
  28. @synthesize statusTextField;
  29. @synthesize inventoryView, armorView, quickView;
  30. @synthesize itemSearchField, itemTableView;
  31. - (void)awakeFromNib
  32. {
  33. [self loadWorldSelectionControl];
  34. armorInventory = [[NSMutableArray alloc] init];
  35. quickInventory = [[NSMutableArray alloc] init];
  36. normalInventory = [[NSMutableArray alloc] init];
  37. statusTextField.stringValue = @"";
  38. [inventoryView setRows:3 columns:9 invert:NO];
  39. [quickView setRows:1 columns:9 invert:NO];
  40. [armorView setRows:4 columns:1 invert:YES];
  41. inventoryView.delegate = self;
  42. quickView.delegate = self;
  43. armorView.delegate = self;
  44. // Item Table View setup
  45. NSArray *keys = [[IJInventoryItem itemIdLookup] allKeys];
  46. keys = [keys sortedArrayUsingSelector:@selector(compare:)];
  47. allItemIds = [[NSArray alloc] initWithArray:keys];
  48. filteredItemIds = [allItemIds retain];
  49. [itemTableView setTarget:self];
  50. [itemTableView setDoubleAction:@selector(itemTableViewDoubleClicked:)];
  51. }
  52. - (void)dealloc
  53. {
  54. [propertiesViewController release];
  55. [armorInventory release];
  56. [quickInventory release];
  57. [normalInventory release];
  58. [inventory release];
  59. [level release];
  60. [player release];
  61. [super dealloc];
  62. }
  63. #pragma mark -
  64. #pragma mark World Selection
  65. - (void)dirtyLoadSheetDidEnd:(NSWindow *)sheet returnCode:(int)returnCode contextInfo:(void *)contextInfo
  66. {
  67. if (returnCode == NSAlertOtherReturn) // Cancel
  68. {
  69. [worldSelectionControl selectItemWithTitle:[loadedWorldFolder lastPathComponent]];
  70. return;
  71. }
  72. if (returnCode == NSAlertDefaultReturn) // Save
  73. {
  74. [self saveWorld];
  75. [self loadWorldAtFolder:attemptedLoadWorldFolder];
  76. }
  77. else if (returnCode == NSAlertAlternateReturn) // Don't save
  78. {
  79. [self setDocumentEdited:NO]; // Slightly hacky -- prevent the alert from being put up again.
  80. [self loadWorldAtFolder:attemptedLoadWorldFolder];
  81. }
  82. }
  83. - (void)loadWorldPlayerInventory:(NSString *)PlayerName
  84. {
  85. /*
  86. * If passing NULL to PlayerName, we will use level.dat instead of
  87. * Players/PlayerName.dat file
  88. */
  89. NSString *playerPath;
  90. [armorInventory removeAllObjects];
  91. [quickInventory removeAllObjects];
  92. [normalInventory removeAllObjects];
  93. [inventoryView setItems:normalInventory];
  94. [quickView setItems:quickInventory];
  95. [armorView setItems:armorInventory];
  96. [player release];
  97. player = nil;
  98. [inventory release];
  99. inventory = nil;
  100. loadedPlayer = nil;
  101. NSLog(@"Player name: %@",PlayerName);
  102. if ([PlayerName isEqualToString: @"World default"])
  103. playerPath = [IJMinecraftLevel pathForPlayer:nil withWorld: loadedWorldFolder];
  104. else
  105. playerPath = [IJMinecraftLevel pathForPlayer:PlayerName withWorld: loadedWorldFolder];
  106. NSLog(@"Path: %@", playerPath);
  107. NSData *playerFileData = [NSData dataWithContentsOfURL:[NSURL fileURLWithPath:playerPath]];
  108. if (!playerFileData)
  109. {
  110. // Error loading
  111. NSBeginCriticalAlertSheet(@"Error loading player.", @"Dismiss", nil, nil, self.window, nil, nil, nil, nil, @"InsideJob was unable to load the level at %@.", playerPath);
  112. return;
  113. }
  114. player = [[IJMinecraftLevel nbtContainerWithData:playerFileData] retain];
  115. inventory = [[player inventory] retain];
  116. // Add placeholder inventory items:
  117. for (int i = 0; i < IJInventorySlotQuickLast + 1 - IJInventorySlotQuickFirst; i++)
  118. [quickInventory addObject:[IJInventoryItem emptyItemWithSlot:IJInventorySlotQuickFirst + i]];
  119. for (int i = 0; i < IJInventorySlotNormalLast + 1 - IJInventorySlotNormalFirst; i++)
  120. [normalInventory addObject:[IJInventoryItem emptyItemWithSlot:IJInventorySlotNormalFirst + i]];
  121. for (int i = 0; i < IJInventorySlotArmorLast + 1 - IJInventorySlotArmorFirst; i++)
  122. [armorInventory addObject:[IJInventoryItem emptyItemWithSlot:IJInventorySlotArmorFirst + i]];
  123. // Overwrite the placeholders with actual inventory:
  124. for (IJInventoryItem *item in inventory)
  125. {
  126. if (IJInventorySlotQuickFirst <= item.slot && item.slot <= IJInventorySlotQuickLast)
  127. {
  128. [quickInventory replaceObjectAtIndex:item.slot - IJInventorySlotQuickFirst withObject:item];
  129. }
  130. else if (IJInventorySlotNormalFirst <= item.slot && item.slot <= IJInventorySlotNormalLast)
  131. {
  132. [normalInventory replaceObjectAtIndex:item.slot - IJInventorySlotNormalFirst withObject:item];
  133. }
  134. else if (IJInventorySlotArmorFirst <= item.slot && item.slot <= IJInventorySlotArmorLast)
  135. {
  136. [armorInventory replaceObjectAtIndex:item.slot - IJInventorySlotArmorFirst withObject:item];
  137. }
  138. }
  139. // NSLog(@"normal: %@", normalInventory);
  140. // NSLog(@"quick: %@", quickInventory);
  141. [inventoryView setItems:normalInventory];
  142. [quickView setItems:quickInventory];
  143. [armorView setItems:armorInventory];
  144. [self setDocumentEdited:NO];
  145. statusTextField.stringValue = @"Player loaded!";
  146. loadedPlayer = [PlayerName retain];
  147. }
  148. - (void)loadWorldAtFolder:(NSString *)worldPath
  149. {
  150. if ([self isDocumentEdited])
  151. {
  152. attemptedLoadWorldFolder = worldPath;
  153. NSBeginInformationalAlertSheet(@"Do you want to save the changes you made in this world?", @"Save", @"Don't Save", @"Cancel", self.window, self, @selector(dirtyLoadSheetDidEnd:returnCode:contextInfo:), nil, nil, @"Your changes will be lost if you do not save them.");
  154. return;
  155. }
  156. NSFileManager *filemgr;
  157. NSArray *filelist;
  158. NSError *fileError;
  159. int count, i;
  160. [armorInventory removeAllObjects];
  161. [quickInventory removeAllObjects];
  162. [normalInventory removeAllObjects];
  163. [inventoryView setItems:normalInventory];
  164. [quickView setItems:quickInventory];
  165. [armorView setItems:armorInventory];
  166. [self willChangeValueForKey:@"worldTime"];
  167. [level release];
  168. level = nil;
  169. [player release];
  170. player = nil;
  171. [inventory release];
  172. inventory = nil;
  173. [self didChangeValueForKey:@"worldTime"];
  174. statusTextField.stringValue = @"No world loaded.";
  175. NSString *levelPath = [IJMinecraftLevel pathForLevelDatAtFolder:worldPath];
  176. NSData *fileData = [NSData dataWithContentsOfURL:[NSURL fileURLWithPath:levelPath]];
  177. sessionLockValue = [IJMinecraftLevel writeToSessionLockAtFolder:worldPath];
  178. if (![IJMinecraftLevel checkSessionLockAtFolder:worldPath value:sessionLockValue])
  179. {
  180. NSBeginCriticalAlertSheet(@"Error loading world.", @"Dismiss", nil, nil, self.window, nil, nil, nil, nil, @"Inside Job was unable obtain the session lock.");
  181. return;
  182. }
  183. if (!fileData)
  184. {
  185. // Error loading
  186. NSBeginCriticalAlertSheet(@"Error loading world.", @"Dismiss", nil, nil, self.window, nil, nil, nil, nil, @"InsideJob was unable to load the level at %@.", levelPath);
  187. return;
  188. }
  189. [self willChangeValueForKey:@"worldTime"];
  190. /* Now search for first player .dat file (but by default try to load from level.dat */
  191. if (!fileData)
  192. {
  193. // Error loading
  194. NSBeginCriticalAlertSheet(@"Error loading world.", @"Dismiss", nil, nil, self.window, nil, nil, nil, nil, @"InsideJob was unable to load the level at %@.", levelPath);
  195. return;
  196. }
  197. [self willChangeValueForKey:@"worldTime"];
  198. [playerSelectionControl setHidden: YES];
  199. /* Now search for first player .dat file (but by default try to load from level.dat */
  200. /* Verify that a "Player" folder exist. If not do nt show the player list */
  201. filemgr = [NSFileManager defaultManager];
  202. filelist = [filemgr contentsOfDirectoryAtPath:worldPath error:&fileError];
  203. count = [filelist count];
  204. for (i = 0; i < count; i++)
  205. {
  206. NSLog (@"File in world/ %@", [filelist objectAtIndex: i]);
  207. if ([[filelist objectAtIndex: i] isEqualTo: @"players"])
  208. {
  209. [playerSelectionControl setHidden: NO];
  210. [self loadPlayerSelectionControl];
  211. break;
  212. }
  213. }
  214. loadedPlayer = nil;
  215. NSString *playerPath = [IJMinecraftLevel pathForPlayer:loadedPlayer withWorld:worldPath];
  216. /* Now load level.dat as if i is not a SMP. */
  217. NSData *playerFileData = [NSData dataWithContentsOfURL:[NSURL fileURLWithPath:playerPath]];
  218. if (!playerFileData)
  219. {
  220. // Error loading
  221. NSBeginCriticalAlertSheet(@"Error loading player.", @"Dismiss", nil, nil, self.window, nil, nil, nil, nil, @"InsideJob was unable to load the level at %@.", playerPath);
  222. return;
  223. }
  224. level = [[IJMinecraftLevel nbtContainerWithData:fileData] retain];
  225. player = [[IJMinecraftLevel nbtContainerWithData:playerFileData] retain];
  226. inventory = [[player inventory] retain];
  227. [self didChangeValueForKey:@"worldTime"];
  228. // Add placeholder inventory items:
  229. for (int i = 0; i < IJInventorySlotQuickLast + 1 - IJInventorySlotQuickFirst; i++)
  230. [quickInventory addObject:[IJInventoryItem emptyItemWithSlot:IJInventorySlotQuickFirst + i]];
  231. for (int i = 0; i < IJInventorySlotNormalLast + 1 - IJInventorySlotNormalFirst; i++)
  232. [normalInventory addObject:[IJInventoryItem emptyItemWithSlot:IJInventorySlotNormalFirst + i]];
  233. for (int i = 0; i < IJInventorySlotArmorLast + 1 - IJInventorySlotArmorFirst; i++)
  234. [armorInventory addObject:[IJInventoryItem emptyItemWithSlot:IJInventorySlotArmorFirst + i]];
  235. // Overwrite the placeholders with actual inventory:
  236. for (IJInventoryItem *item in inventory)
  237. {
  238. if (IJInventorySlotQuickFirst <= item.slot && item.slot <= IJInventorySlotQuickLast)
  239. {
  240. [quickInventory replaceObjectAtIndex:item.slot - IJInventorySlotQuickFirst withObject:item];
  241. }
  242. else if (IJInventorySlotNormalFirst <= item.slot && item.slot <= IJInventorySlotNormalLast)
  243. {
  244. [normalInventory replaceObjectAtIndex:item.slot - IJInventorySlotNormalFirst withObject:item];
  245. }
  246. else if (IJInventorySlotArmorFirst <= item.slot && item.slot <= IJInventorySlotArmorLast)
  247. {
  248. [armorInventory replaceObjectAtIndex:item.slot - IJInventorySlotArmorFirst withObject:item];
  249. }
  250. }
  251. // NSLog(@"normal: %@", normalInventory);
  252. // NSLog(@"quick: %@", quickInventory);
  253. [inventoryView setItems:normalInventory];
  254. [quickView setItems:quickInventory];
  255. [armorView setItems:armorInventory];
  256. [self setDocumentEdited:NO];
  257. statusTextField.stringValue = @"World loaded!";
  258. loadedWorldFolder = [worldPath retain];
  259. NSLog(@"%@",loadedWorldFolder);
  260. NSLog(@"%@",worldPath);
  261. }
  262. - (void)loadWorldAtIndex:(int)worldIndex
  263. {
  264. NSString *worldPath;
  265. worldPath = [IJMinecraftLevel pathForWorldAtIndex:worldIndex];
  266. [self loadWorldAtFolder: worldPath];
  267. }
  268. - (void)saveWorld
  269. {
  270. NSString *worldPath = loadedWorldFolder;
  271. if (inventory == nil)
  272. return; // no world loaded, nothing to save
  273. if (![IJMinecraftLevel checkSessionLockAtFolder:worldPath value:sessionLockValue])
  274. {
  275. NSBeginCriticalAlertSheet(@"Another application has modified this world.", @"Dismiss", nil, nil, self.window, nil, nil, nil, nil, @"The session lock was changed by another application.");
  276. return;
  277. }
  278. NSString *levelPath = [IJMinecraftLevel pathForLevelDatAtFolder:worldPath];
  279. NSString *playerPath = [IJMinecraftLevel pathForPlayer:loadedPlayer withWorld:worldPath];
  280. NSMutableArray *newInventory = [NSMutableArray array];
  281. for (NSArray *items in [NSArray arrayWithObjects:armorInventory, quickInventory, normalInventory, nil])
  282. {
  283. for (IJInventoryItem *item in items)
  284. {
  285. if (item.count > 0 && item.itemId > 0)
  286. [newInventory addObject:item];
  287. }
  288. }
  289. [player setInventory:newInventory];
  290. NSString *backupLevelPath = [levelPath stringByAppendingPathExtension:@"insidejobbackup"];
  291. NSString *backupPlayerPath = [playerPath stringByAppendingPathExtension:@"insidejobbackup"];
  292. BOOL success = NO;
  293. NSError *error = nil;
  294. // Remove a previously-created .insidejobbackup, if it exists:
  295. if ([[NSFileManager defaultManager] fileExistsAtPath:backupLevelPath])
  296. {
  297. success = [[NSFileManager defaultManager] removeItemAtPath:backupLevelPath error:&error];
  298. if (!success)
  299. {
  300. NSLog(@"%s:%d %@", __PRETTY_FUNCTION__, __LINE__, [error localizedDescription]);
  301. NSBeginCriticalAlertSheet(@"An error occurred while saving.", @"Dismiss", nil, nil, self.window, nil, nil, nil, nil, @"Inside Job was unable to remove the prior backup of this level file:\n%@", [error localizedDescription]);
  302. return;
  303. }
  304. }
  305. // Create the backup:
  306. success = [[NSFileManager defaultManager] copyItemAtPath:levelPath toPath:backupLevelPath error:&error];
  307. if (!success)
  308. {
  309. NSLog(@"%s:%d %@", __PRETTY_FUNCTION__, __LINE__, [error localizedDescription]);
  310. NSBeginCriticalAlertSheet(@"An error occurred while saving.", @"Dismiss", nil, nil, self.window, nil, nil, nil, nil, @"Inside Job was unable to create a backup of the existing level file:\n%@", [error localizedDescription]);
  311. return;
  312. }
  313. // Write the new level.dat out:
  314. success = [[player writeData] writeToURL:[NSURL fileURLWithPath:levelPath] options:0 error:&error];
  315. if (!success)
  316. {
  317. NSLog(@"%s:%d %@", __PRETTY_FUNCTION__, __LINE__, [error localizedDescription]);
  318. NSError *restoreError = nil;
  319. success = [[NSFileManager defaultManager] copyItemAtPath:backupLevelPath toPath:levelPath error:&restoreError];
  320. if (!success)
  321. {
  322. NSLog(@"%s:%d %@", __PRETTY_FUNCTION__, __LINE__, [restoreError localizedDescription]);
  323. NSBeginCriticalAlertSheet(@"An error occurred while saving.", @"Dismiss", nil, nil, self.window, nil, nil, nil, nil, @"Inside Job was unable to save to the existing level file, and the backup could not be restored.\n%@\n%@", [error localizedDescription], [restoreError localizedDescription]);
  324. }
  325. else
  326. {
  327. NSBeginCriticalAlertSheet(@"An error occurred while saving.", @"Dismiss", nil, nil, self.window, nil, nil, nil, nil, @"Inside Job was unable to save to the existing level file, and the backup was successfully restored.\n%@", [error localizedDescription]);
  328. }
  329. return;
  330. }
  331. if (playerPath != levelPath)
  332. {
  333. // Remove a previously-created .insidejobbackup, if it exists:
  334. if ([[NSFileManager defaultManager] fileExistsAtPath:backupPlayerPath])
  335. {
  336. success = [[NSFileManager defaultManager] removeItemAtPath:backupPlayerPath error:&error];
  337. if (!success)
  338. {
  339. NSLog(@"%s:%d %@", __PRETTY_FUNCTION__, __LINE__, [error localizedDescription]);
  340. NSBeginCriticalAlertSheet(@"An error occurred while saving.", @"Dismiss", nil, nil, self.window, nil, nil, nil, nil, @"Inside Job was unable to remove the prior backup of this player file:\n%@", [error localizedDescription]);
  341. return;
  342. }
  343. }
  344. success = [[NSFileManager defaultManager] copyItemAtPath:playerPath toPath:backupPlayerPath error:&error];
  345. if (!success)
  346. {
  347. NSLog(@"%s:%d %@", __PRETTY_FUNCTION__, __LINE__, [error localizedDescription]);
  348. NSBeginCriticalAlertSheet(@"An error occurred while saving.", @"Dismiss", nil, nil, self.window, nil, nil, nil, nil, @"Inside Job was unable to create a backup of the existing player file:\n%@", [error localizedDescription]);
  349. return;
  350. }
  351. // Write the new player.dat out:
  352. success = [[player writeData] writeToURL:[NSURL fileURLWithPath:playerPath] options:0 error:&error];
  353. if (!success)
  354. {
  355. NSLog(@"%s:%d %@", __PRETTY_FUNCTION__, __LINE__, [error localizedDescription]);
  356. NSError *restoreError = nil;
  357. success = [[NSFileManager defaultManager] copyItemAtPath:backupPlayerPath toPath:playerPath error:&restoreError];
  358. if (!success)
  359. {
  360. NSLog(@"%s:%d %@", __PRETTY_FUNCTION__, __LINE__, [restoreError localizedDescription]);
  361. NSBeginCriticalAlertSheet(@"An error occurred while saving.", @"Dismiss", nil, nil, self.window, nil, nil, nil, nil, @"Inside Job was unable to save to the existing player file, and the backup could not be restored.\n%@\n%@", [error localizedDescription], [restoreError localizedDescription]);
  362. }
  363. else
  364. {
  365. NSBeginCriticalAlertSheet(@"An error occurred while saving.", @"Dismiss", nil, nil, self.window, nil, nil, nil, nil, @"Inside Job was unable to save to the existing player file, and the backup was successfully restored.\n%@", [error localizedDescription]);
  366. }
  367. return;
  368. }
  369. }
  370. [self setDocumentEdited:NO];
  371. statusTextField.stringValue = @"Saved.";
  372. }
  373. - (void)setDocumentEdited:(BOOL)edited
  374. {
  375. [super setDocumentEdited:edited];
  376. if (edited)
  377. statusTextField.stringValue = @"World has unsaved changes.";
  378. }
  379. - (BOOL)isDocumentEdited
  380. {
  381. return [self.window isDocumentEdited];
  382. }
  383. #pragma mark -
  384. #pragma mark Actions
  385. - (IBAction)menuSelectWorld:(id)sender
  386. {
  387. int worldIndex = [sender tag];
  388. [self loadWorldAtIndex:worldIndex];
  389. [worldSelectionControl selectItemWithTitle:[loadedWorldFolder lastPathComponent]];
  390. }
  391. - (IBAction)menuSelectWorldFromPath:(id)sender
  392. {
  393. NSInteger openResult;
  394. /* Ask user for world folder path */
  395. NSOpenPanel *panel = [NSOpenPanel openPanel];
  396. NSString *worldPath;
  397. /* Only allow to choose a folder */
  398. [panel setCanChooseDirectories:YES];
  399. [panel setCanChooseFiles:NO];
  400. openResult = [panel runModal];
  401. if (openResult == NSOKButton)
  402. {
  403. worldPath = [[panel directoryURL] path];
  404. /* Verify for level.dat */
  405. if (![IJMinecraftLevel worldExistsAtFolder: worldPath])
  406. {
  407. NSBeginCriticalAlertSheet(@"No world exists in that slot.", @"Dismiss", nil, nil, self.window, nil, nil, nil, nil, @"Please create a new single player world in this slot using Minecraft and try again.");
  408. return;
  409. }
  410. /* Now try to open the world... */
  411. [self loadWorldAtFolder:[[panel directoryURL] path]];
  412. [worldSelectionControl addItemWithTitle:[loadedWorldFolder lastPathComponent]];
  413. [worldSelectionControl selectItemWithTitle:[loadedWorldFolder lastPathComponent]];
  414. }
  415. }
  416. - (IBAction)worldSelectionChanged:(id)sender
  417. {
  418. NSArray *paths = NSSearchPathForDirectoriesInDomains(NSApplicationSupportDirectory, NSUserDomainMask, YES);
  419. NSString *path = [paths objectAtIndex:0];
  420. path = [path stringByAppendingPathComponent:@"minecraft"];
  421. path = [path stringByAppendingPathComponent:@"saves"];
  422. NSString* worldName = [worldSelectionControl titleOfSelectedItem];
  423. NSString* worldPath = [path stringByAppendingPathComponent:worldName];
  424. NSLog(@"loadedWorldFolder: %@",loadedWorldFolder);
  425. NSLog(@"worldName: %@",worldName);
  426. NSLog(@"worldPath: %@",worldPath);
  427. [self loadWorldAtFolder:worldPath];
  428. }
  429. - (void)loadWorldSelectionControl
  430. {
  431. NSArray *paths = NSSearchPathForDirectoriesInDomains(NSApplicationSupportDirectory, NSUserDomainMask, YES);
  432. NSString *path = [paths objectAtIndex:0];
  433. path = [path stringByAppendingPathComponent:@"minecraft"];
  434. path = [path stringByAppendingPathComponent:@"saves"];
  435. NSFileManager *filemgr;
  436. NSArray *filelist;
  437. NSError *fileError;
  438. int count;
  439. int i;
  440. filemgr = [NSFileManager defaultManager];
  441. filelist = [filemgr contentsOfDirectoryAtPath:path error:&fileError];
  442. count = [filelist count];
  443. [worldSelectionControl removeAllItems];
  444. for (i = 0; i < count; i++)
  445. {
  446. NSLog (@"%@", [filelist objectAtIndex: i]);
  447. if([IJMinecraftLevel worldExistsAtFolder:[path stringByAppendingPathComponent:[filelist objectAtIndex: i]]])
  448. [worldSelectionControl addItemWithTitle:[filelist objectAtIndex: i]];
  449. }
  450. [filemgr release];
  451. }
  452. - (void)loadPlayerSelectionControl
  453. {
  454. NSString *playerPath = loadedWorldFolder;
  455. playerPath = [playerPath stringByAppendingPathComponent:@"players"];
  456. NSFileManager *filemgr;
  457. NSArray *filelist;
  458. NSError *fileError;
  459. int count;
  460. int i;
  461. filemgr = [NSFileManager defaultManager];
  462. filelist = [filemgr contentsOfDirectoryAtPath:playerPath error:&fileError];
  463. count = [filelist count];
  464. [playerSelectionControl removeAllItems];
  465. [playerSelectionControl addItemWithTitle:@"World default"];
  466. for (i = 0; i < count; i++)
  467. {
  468. NSLog (@"%@", [filelist objectAtIndex: i]);
  469. /* Get only .dat file */
  470. if ([[[filelist objectAtIndex: i] pathExtension] isEqualToString:@"dat"])
  471. [playerSelectionControl addItemWithTitle:[[filelist objectAtIndex: i] stringByDeletingPathExtension]];
  472. }
  473. [filemgr release];
  474. }
  475. - (void)saveDocument:(id)sender
  476. {
  477. [self saveWorld];
  478. }
  479. - (void)delete:(id)sender
  480. {
  481. // IJInventoryItem *item = [outlineView itemAtRow:[outlineView selectedRow]];
  482. // item.count = 0;
  483. // item.itemId = 0;
  484. // item.damage = 0;
  485. // [self setDocumentEdited:YES];
  486. // [outlineView reloadItem:item];
  487. }
  488. - (IBAction)makeSearchFieldFirstResponder:(id)sender
  489. {
  490. [itemSearchField becomeFirstResponder];
  491. }
  492. - (BOOL)validateUserInterfaceItem:(id <NSValidatedUserInterfaceItem>)anItem
  493. {
  494. if (anItem.action == @selector(saveDocument:))
  495. return inventory != nil;
  496. return YES;
  497. }
  498. - (NSNumber *)worldTime
  499. {
  500. return [level worldTimeContainer].numberValue;
  501. }
  502. - (void)setWorldTime:(NSNumber *)number
  503. {
  504. [self willChangeValueForKey:@"worldTime"];
  505. [level worldTimeContainer].numberValue = number;
  506. [self didChangeValueForKey:@"worldTime"];
  507. [self setDocumentEdited:YES];
  508. }
  509. - (void)calcTimePoints:(int)number
  510. {
  511. int result;
  512. int wTime = [[self worldTime] intValue];
  513. result =wTime +(number - (wTime % number));
  514. NSNumber *newTime = [NSNumber numberWithInt:result];
  515. [self setWorldTime:newTime];
  516. }
  517. - (IBAction)setNextDay:(id)sender
  518. {
  519. int number = 24000;
  520. [self calcTimePoints:number];
  521. }
  522. - (IBAction)setNextNight:(id)sender
  523. {
  524. int number = 12000;
  525. [self calcTimePoints:number];
  526. }
  527. - (IBAction)setNextMidnight:(id)sender
  528. {
  529. int number = 18000;
  530. [self calcTimePoints:number];
  531. }
  532. - (IBAction)setNextNoon:(id)sender
  533. {
  534. int number = 6000;
  535. [self calcTimePoints:number];
  536. }
  537. - (void)clearInventory{
  538. [armorInventory removeAllObjects];
  539. [quickInventory removeAllObjects];
  540. [normalInventory removeAllObjects];
  541. [inventoryView setItems:normalInventory];
  542. [quickView setItems:quickInventory];
  543. [armorView setItems:armorInventory];
  544. [self setDocumentEdited:YES];
  545. }
  546. - (void)saveInventory
  547. {
  548. NSString *path = [[NSFileManager defaultManager] applicationSupportDirectory];
  549. NSLog(@"%@",path);
  550. NSString *file = @"Inventory.plist";
  551. NSString *InventoryPath = [path stringByAppendingPathComponent:file];
  552. NSLog(@"%@",InventoryPath);
  553. NSMutableArray *newInventory = [NSMutableArray array];
  554. for (NSArray *items in [NSArray arrayWithObjects:armorInventory, quickInventory, normalInventory, nil])
  555. {
  556. for (IJInventoryItem *item in items)
  557. {
  558. if (item.count > 0 && item.itemId > 0)
  559. [newInventory addObject:item];
  560. }
  561. }
  562. [NSKeyedArchiver archiveRootObject: newInventory toFile:InventoryPath];
  563. }
  564. -(void)loadInventory
  565. {
  566. NSString *path = [[NSFileManager defaultManager] applicationSupportDirectory];
  567. NSString *file = @"Inventory.plist";
  568. NSString *InventoryPath = [path stringByAppendingPathComponent:file];
  569. [self clearInventory];
  570. NSArray *newInventory = [NSKeyedUnarchiver unarchiveObjectWithFile:InventoryPath];
  571. for (int i = 0; i < IJInventorySlotQuickLast + 1 - IJInventorySlotQuickFirst; i++)
  572. [quickInventory addObject:[IJInventoryItem emptyItemWithSlot:IJInventorySlotQuickFirst + i]];
  573. for (int i = 0; i < IJInventorySlotNormalLast + 1 - IJInventorySlotNormalFirst; i++)
  574. [normalInventory addObject:[IJInventoryItem emptyItemWithSlot:IJInventorySlotNormalFirst + i]];
  575. for (int i = 0; i < IJInventorySlotArmorLast + 1 - IJInventorySlotArmorFirst; i++)
  576. [armorInventory addObject:[IJInventoryItem emptyItemWithSlot:IJInventorySlotArmorFirst + i]];
  577. for (IJInventoryItem *item in newInventory)
  578. {
  579. if (IJInventorySlotQuickFirst <= item.slot && item.slot <= IJInventorySlotQuickLast)
  580. {
  581. [quickInventory replaceObjectAtIndex:item.slot - IJInventorySlotQuickFirst withObject:item];
  582. }
  583. else if (IJInventorySlotNormalFirst <= item.slot && item.slot <= IJInventorySlotNormalLast)
  584. {
  585. [normalInventory replaceObjectAtIndex:item.slot - IJInventorySlotNormalFirst withObject:item];
  586. }
  587. else if (IJInventorySlotArmorFirst <= item.slot && item.slot <= IJInventorySlotArmorLast)
  588. {
  589. [armorInventory replaceObjectAtIndex:item.slot - IJInventorySlotArmorFirst withObject:item];
  590. }
  591. }
  592. [inventoryView setItems:normalInventory];
  593. [quickView setItems:quickInventory];
  594. [armorView setItems:armorInventory];
  595. }
  596. - (IBAction)emptyInventory:(id)sender
  597. {
  598. [self clearInventory];
  599. }
  600. - (IBAction)saveInventoryItems:(id)sender
  601. {
  602. [self saveInventory];
  603. }
  604. - (IBAction)loadInventoryItems:(id)sender
  605. {
  606. [self loadInventory];
  607. }
  608. - (IBAction)playerSelectionChanged:(id)sender
  609. {
  610. [self loadWorldPlayerInventory: [playerSelectionControl titleOfSelectedItem]];
  611. }
  612. #pragma mark -
  613. #pragma mark IJInventoryViewDelegate
  614. - (IJInventoryView *)inventoryViewForItemArray:(NSMutableArray *)theItemArray
  615. {
  616. if (theItemArray == normalInventory)
  617. return inventoryView;
  618. if (theItemArray == quickInventory)
  619. return quickView;
  620. if (theItemArray == armorInventory)
  621. return armorView;
  622. return nil;
  623. }
  624. - (NSMutableArray *)itemArrayForInventoryView:(IJInventoryView *)theInventoryView slotOffset:(int*)slotOffset
  625. {
  626. if (theInventoryView == inventoryView)
  627. {
  628. if (slotOffset) *slotOffset = IJInventorySlotNormalFirst;
  629. return normalInventory;
  630. }
  631. else if (theInventoryView == quickView)
  632. {
  633. if (slotOffset) *slotOffset = IJInventorySlotQuickFirst;
  634. return quickInventory;
  635. }
  636. else if (theInventoryView == armorView)
  637. {
  638. if (slotOffset) *slotOffset = IJInventorySlotArmorFirst;
  639. return armorInventory;
  640. }
  641. return nil;
  642. }
  643. - (void)inventoryView:(IJInventoryView *)theInventoryView removeItemAtIndex:(int)itemIndex
  644. {
  645. int slotOffset = 0;
  646. NSMutableArray *itemArray = [self itemArrayForInventoryView:theInventoryView slotOffset:&slotOffset];
  647. if (itemArray)
  648. {
  649. IJInventoryItem *item = [IJInventoryItem emptyItemWithSlot:slotOffset + itemIndex];
  650. [itemArray replaceObjectAtIndex:itemIndex withObject:item];
  651. [theInventoryView setItems:itemArray];
  652. }
  653. [self setDocumentEdited:YES];
  654. }
  655. - (void)inventoryView:(IJInventoryView *)theInventoryView setItem:(IJInventoryItem *)item atIndex:(int)itemIndex
  656. {
  657. int slotOffset = 0;
  658. NSMutableArray *itemArray = [self itemArrayForInventoryView:theInventoryView slotOffset:&slotOffset];
  659. if (itemArray)
  660. {
  661. [itemArray replaceObjectAtIndex:itemIndex withObject:item];
  662. item.slot = slotOffset + itemIndex;
  663. [theInventoryView setItems:itemArray];
  664. }
  665. [self setDocumentEdited:YES];
  666. }
  667. - (void)inventoryView:(IJInventoryView *)theInventoryView selectedItemAtIndex:(int)itemIndex
  668. {
  669. // Show the properties window for this item.
  670. IJInventoryItem *lastItem = propertiesViewController.item;
  671. NSPoint itemLocationInView = [theInventoryView pointForItemAtIndex:itemIndex];
  672. NSPoint point = [theInventoryView convertPoint:itemLocationInView toView:nil];
  673. point.x += 16 + 8;
  674. point.y -= 16;
  675. NSArray *items = [self itemArrayForInventoryView:theInventoryView slotOffset:nil];
  676. IJInventoryItem *item = [items objectAtIndex:itemIndex];
  677. //NSLog(@"%s index=%d item=%@", _cmd, itemIndex, item);
  678. if (item.itemId == 0 || lastItem == item)
  679. {
  680. // Perhaps caused by a bug, but it seems to be possible for the window to not be invisible at this point,
  681. // so we will set the alpha value here to be sure.
  682. [propertiesWindow setAlphaValue:0.0];
  683. propertiesViewController.item = nil;
  684. return; // can't show info on nothing
  685. }
  686. if (!propertiesViewController)
  687. {
  688. propertiesViewController = [[IJItemPropertiesViewController alloc] initWithNibName:@"ItemPropertiesView" bundle:nil];
  689. propertiesWindow = [[MAAttachedWindow alloc] initWithView:propertiesViewController.view
  690. attachedToPoint:point
  691. inWindow:self.window
  692. onSide:MAPositionRight
  693. atDistance:0];
  694. [propertiesWindow setBackgroundColor:[NSColor controlBackgroundColor]];
  695. [propertiesWindow setViewMargin:4.0];
  696. [propertiesWindow setAlphaValue:1.0];
  697. [[self window] addChildWindow:propertiesWindow ordered:NSWindowAbove];
  698. }
  699. if (observerObject)
  700. [[NSNotificationCenter defaultCenter] removeObserver:observerObject];
  701. observerObject = [[NSNotificationCenter defaultCenter] addObserverForName:NSWindowDidResignKeyNotification
  702. object:propertiesWindow
  703. queue:[NSOperationQueue mainQueue]
  704. usingBlock:^(NSNotification *notification) {
  705. [propertiesViewController commitEditing];
  706. if (item.count == 0)
  707. item.itemId = 0;
  708. [theInventoryView reloadItemAtIndex:itemIndex];
  709. [propertiesWindow setAlphaValue:0.0];
  710. }];
  711. propertiesViewController.item = item;
  712. if (propertiesViewController.item.damage == -1000){
  713. [propertiesViewController setState:YES];
  714. }else {
  715. [propertiesViewController setState:NO];
  716. }
  717. [propertiesWindow setPoint:point side:MAPositionRight];
  718. [propertiesWindow makeKeyAndOrderFront:nil];
  719. [propertiesWindow setAlphaValue:1.0];
  720. }
  721. #pragma mark -
  722. #pragma mark Item Picker
  723. - (IBAction)updateItemSearchFilter:(id)sender
  724. {
  725. NSString *filterString = [sender stringValue];
  726. if (filterString.length == 0)
  727. {
  728. [filteredItemIds autorelease];
  729. filteredItemIds = [allItemIds retain];
  730. [itemTableView reloadData];
  731. return;
  732. }
  733. NSMutableArray *results = [NSMutableArray array];
  734. for (NSNumber *itemId in allItemIds)
  735. {
  736. NSString *name = [[IJInventoryItem itemIdLookup] objectForKey:itemId];
  737. NSRange range = [name rangeOfString:filterString options:NSCaseInsensitiveSearch];
  738. if (range.location != NSNotFound)
  739. {
  740. [results addObject:itemId];
  741. continue;
  742. }
  743. // Also search the item id:
  744. range = [[itemId stringValue] rangeOfString:filterString];
  745. if (range.location != NSNotFound)
  746. {
  747. [results addObject:itemId];
  748. continue;
  749. }
  750. }
  751. [filteredItemIds autorelease];
  752. filteredItemIds = [results retain];
  753. [itemTableView reloadData];
  754. }
  755. - (NSInteger)numberOfRowsInTableView:(NSTableView *)theTableView
  756. {
  757. return filteredItemIds.count;
  758. }
  759. - (id)tableView:(NSTableView *)theTableView objectValueForTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)row
  760. {
  761. NSNumber *itemId = [filteredItemIds objectAtIndex:row];
  762. if ([tableColumn.identifier isEqual:@"itemId"])
  763. {
  764. return itemId;
  765. }
  766. else if ([tableColumn.identifier isEqual:@"image"])
  767. {
  768. return [IJInventoryItem imageForItemId:[itemId shortValue]];
  769. }
  770. else
  771. {
  772. NSString *name = [[IJInventoryItem itemIdLookup] objectForKey:itemId];
  773. return name;
  774. }
  775. }
  776. - (BOOL)tableView:(NSTableView *)tableView writeRowsWithIndexes:(NSIndexSet *)rowIndexes toPasteboard:(NSPasteboard *)pboard
  777. {
  778. [pboard declareTypes:[NSArray arrayWithObjects:IJPasteboardTypeInventoryItem, nil] owner:nil];
  779. NSNumber *itemId = [filteredItemIds objectAtIndex:[rowIndexes firstIndex]];
  780. IJInventoryItem *item = [[IJInventoryItem alloc] init];
  781. item.itemId = [itemId shortValue];
  782. item.count = 1;
  783. item.damage = 0;
  784. item.slot = 0;
  785. [pboard setData:[NSKeyedArchiver archivedDataWithRootObject:item]
  786. forType:IJPasteboardTypeInventoryItem];
  787. [item release];
  788. return YES;
  789. }
  790. - (NSMutableArray *)inventoryArrayWithEmptySlot:(NSUInteger *)slot
  791. {
  792. for (NSMutableArray *inventoryArray in [NSArray arrayWithObjects:quickInventory, normalInventory, nil])
  793. {
  794. __block BOOL found = NO;
  795. [inventoryArray enumerateObjectsUsingBlock:^(id obj, NSUInteger index, BOOL *stop) {
  796. IJInventoryItem *item = obj;
  797. if (item.count == 0)
  798. {
  799. *slot = index;
  800. *stop = YES;
  801. found = YES;
  802. }
  803. }];
  804. if (found)
  805. return inventoryArray;
  806. }
  807. return nil;
  808. }
  809. - (IBAction)itemTableViewDoubleClicked:(id)sender
  810. {
  811. NSUInteger slot;
  812. NSMutableArray *inventoryArray = [self inventoryArrayWithEmptySlot:&slot];
  813. if (!inventoryArray)
  814. return;
  815. IJInventoryItem *item = [inventoryArray objectAtIndex:slot];
  816. item.itemId = [[filteredItemIds objectAtIndex:[itemTableView selectedRow]] shortValue];
  817. item.count = 1;
  818. [self setDocumentEdited:YES];
  819. IJInventoryView *invView = [self inventoryViewForItemArray:inventoryArray];
  820. [invView reloadItemAtIndex:slot];
  821. [self inventoryView:invView selectedItemAtIndex:slot];
  822. }
  823. #pragma mark -
  824. #pragma mark NSWindowDelegate
  825. - (void)dirtyCloseSheetDidDismiss:(NSWindow *)sheet returnCode:(int)returnCode contextInfo:(void *)contextInfo
  826. {
  827. if (returnCode == NSAlertOtherReturn) // Cancel
  828. return;
  829. if (returnCode == NSAlertDefaultReturn) // Save
  830. {
  831. [self saveWorld];
  832. [self.window performClose:nil];
  833. }
  834. else if (returnCode == NSAlertAlternateReturn) // Don't save
  835. {
  836. [self setDocumentEdited:NO]; // Slightly hacky -- prevent the alert from being put up again.
  837. [self.window performClose:nil];
  838. }
  839. }
  840. - (BOOL)windowShouldClose:(id)sender
  841. {
  842. if ([self isDocumentEdited])
  843. {
  844. // Note: We use the didDismiss selector becuase the sheet needs to be closed in order for performClose: to work.
  845. NSBeginInformationalAlertSheet(@"Do you want to save the changes you made in this world?", @"Save", @"Don't Save", @"Cancel", self.window, self, nil, @selector(dirtyCloseSheetDidDismiss:returnCode:contextInfo:), nil, @"Your changes will be lost if you do not save them.");
  846. return NO;
  847. }
  848. return YES;
  849. }
  850. - (void)windowWillClose:(NSNotification *)notification
  851. {
  852. [NSApp terminate:nil];
  853. }
  854. #pragma mark -
  855. #pragma mark NSControlTextEditingDelegate
  856. - (BOOL)control:(NSControl *)control textView:(NSTextView *)textView doCommandBySelector:(SEL)command
  857. {
  858. if (command == @selector(moveDown:))
  859. {
  860. if ([itemTableView numberOfRows] > 0)
  861. {
  862. [self.window makeFirstResponder:itemTableView];
  863. [itemTableView selectRowIndexes:[NSIndexSet indexSetWithIndex:0] byExtendingSelection:NO];
  864. }
  865. return YES;
  866. }
  867. return YES;
  868. }
  869. @end