選択できるのは25トピックまでです。 トピックは、先頭が英数字で、英数字とダッシュ('-')を使用した35文字以内のものにしてください。

TurnIn.lua 16KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697
  1. --[[
  2. Turn-In Mod
  3. version 2.1
  4. Authored by Ian Friedman
  5. Sabindeus of Smolderthorn (Alliance)
  6. The repeatable quest turn in automating machine.
  7. Thanks to Arcanemagus of Hyjal for extra bug fixes and coding input
  8. ]]
  9. TI_VersionString = "2.0";
  10. local TI_slashtable;
  11. TI_gossipclosed = false;
  12. TI_LoadedNPCIndex = 0;
  13. TI_activenumber = 1;
  14. TI_availnumber = 1;
  15. TI_specnum = 0;
  16. TI_gossipopts = {};
  17. TI_TempNPCList = {};
  18. TI_TempNPCListMaxSize = 5;
  19. TI_NPCInQuestion = nil;
  20. local TI_GossipDefaults = {
  21. availquest ="Available Quests",
  22. activequest = "Active Quests",
  23. gossip = "Gossip",
  24. vendor = "Vendor",
  25. taxi = "Taxi",
  26. trainer = "Trainer",
  27. battlemaster = "Battlemaster",
  28. binder = "Hearthstone Binder",
  29. healer = "Spirit Healer",
  30. banker = "Bank"
  31. };
  32. local TI_FunctionList = {
  33. g = {
  34. availquest = SelectGossipAvailableQuest,
  35. activequest = SelectGossipActiveQuest,
  36. default = SelectGossipOption
  37. },
  38. q = {
  39. availquest = SelectAvailableQuest,
  40. activequest = SelectActiveQuest
  41. }
  42. };
  43. local TI_DefaultStatus = {
  44. state = false,
  45. version = TI_VersionString,
  46. options = {},
  47. debugstate = false,
  48. usedefault=true,
  49. autoadd=false
  50. };
  51. local TI_events = {
  52. "GOSSIP_SHOW",
  53. "GOSSIP_CLOSED",
  54. "QUEST_DETAIL",
  55. "QUEST_COMPLETE",
  56. "QUEST_PROGRESS",
  57. "QUEST_GREETING",
  58. "QUEST_FINISHED"
  59. };
  60. TI_TempNPCList = {};
  61. function TI_message(...)
  62. local x = {...};
  63. for k,v in pairs(x) do
  64. DEFAULT_CHAT_FRAME:AddMessage(tostring(v));
  65. end
  66. end
  67. function TI_debug(...)
  68. if(TI_status.debugstate) then
  69. TI_message(...)
  70. end
  71. end
  72. function TI_OnLoad()
  73. SlashCmdList["TI"]=TI_SlashCmdHandler;
  74. SLASH_TI1="/turnin";
  75. SLASH_TI2="/ti";
  76. tinsert(UISpecialFrames,"TI_OptionsFrame");
  77. TI_message("Turn In loaded");
  78. TI_slashtable = {};
  79. TI_gossipopts = {};
  80. TI_gossipclosed = false;
  81. TurnIn:RegisterEvent("VARIABLES_LOADED");
  82. TI_activenumber = 1;
  83. TI_availnumber = 1;
  84. TI_specnum = 0;
  85. StaticPopupDialogs["TI_NPCINQUESTION"] = {
  86. text = "The NPC %s is already in your NPC Database. Do you wish to replace his gossip options with the current ones? (Note: This will overwrite your settings for this NPC.)",
  87. button1 = "Yes",
  88. button2 = "No",
  89. OnAccept = function()
  90. TI_AddNPCToList(TI_NPCInQuestion.list, TI_NPCInQuestion.name, true);
  91. end,
  92. timeout=0,
  93. whileDead = 1,
  94. hideOnEscape = 1
  95. };
  96. end
  97. function TI_VarInit()
  98. if(not TI_status or TI_status.version ~= TI_VersionString) then
  99. TI_status = TI_copyTable(TI_DefaultStatus);
  100. TI_OptionsInit();
  101. end
  102. if(not TI_status.options or #TI_status.options == 0) then
  103. TI_OptionsInit();
  104. end
  105. if(not TI_NPCDB) then
  106. TI_NPCDB = {};
  107. end
  108. if(not TI_NPCIndex) then
  109. TI_NPCIndexGenerate();
  110. end
  111. TI_PopulateOptions("vars loaded");
  112. end
  113. function TI_OptionsInit()
  114. TI_status.options = {};
  115. for k,v in pairs(TI_GossipDefaults) do
  116. local temp = {};
  117. temp.name = v;
  118. temp.type = k;
  119. temp.state = false;
  120. table.insert(TI_status.options, temp);
  121. end
  122. end
  123. function TI_NPCIndexGenerate()
  124. TI_NPCIndex = {};
  125. for k,v in pairs(TI_NPCDB) do
  126. table.insert(TI_NPCIndex, k);
  127. end
  128. table.sort(TI_NPCIndex);
  129. end
  130. function TI_LoadEvents()
  131. for k,v in pairs(TI_events) do
  132. TurnIn:RegisterEvent(v);
  133. end
  134. end
  135. function TI_ResetPointers()
  136. TI_activenumber = 1;
  137. TI_availnumber = 1;
  138. TI_specnum = 0;
  139. end
  140. function TI_UnloadEvents()
  141. for k,v in pairs(TI_events) do
  142. TurnIn:UnregisterEvent(v);
  143. end
  144. end
  145. function TI_Switch(state)
  146. if(state=="on") then
  147. TI_status.state = true;
  148. TI_LoadEvents();
  149. TI_message("Turn In On");
  150. elseif(state=="off") then
  151. TI_ResetPointers();
  152. TI_status.state = false;
  153. TI_UnloadEvents();
  154. TI_message("Turn In Off");
  155. elseif(state=="toggle") then
  156. if(TI_status.state) then
  157. TI_Switch("off");
  158. else
  159. TI_Switch("on");
  160. end
  161. end
  162. TI_StatusIndicatorUpdate();
  163. end
  164. function TI_SlashCmdHandler(cmd)
  165. cmdlist = {strsplit(" ", cmd)};
  166. local commands = {
  167. on = function ()
  168. TI_Switch("on");
  169. end,
  170. off = function ()
  171. TI_Switch("off");
  172. end,
  173. toggle = function ()
  174. TI_Switch("toggle");
  175. end,
  176. status = function ()
  177. if(TI_status.state) then
  178. TI_message("Turn In On");
  179. else
  180. TI_message("Turn In Off");
  181. end
  182. end,
  183. window = function ()
  184. TI_OptionsFrame:Show();
  185. end,
  186. config = function ()
  187. TI_OptionsFrame:Show();
  188. end,
  189. recent = function ()
  190. TI_TempNPCListWindow:Show();
  191. end,
  192. debug = function ()
  193. if(TI_status.debugstate) then
  194. TI_status.debugstate = false;
  195. TI_message("debug mode off");
  196. else
  197. TI_status.debugstate = true;
  198. TI_message("debug mode on");
  199. end
  200. end
  201. };
  202. if(commands[cmdlist[1]]) then
  203. commands[cmdlist[1]](cmdlist[2], cmdlist[3], cmdlist[4]);
  204. else
  205. TI_message("Turn In 2.1 Help", "--------------", "/ti on - turns Turn In on", "/ti off - turns Turn In off", "/ti toggle - toggles Turn In on or off", "/ti window - shows the options window", "/ti recent - shows the recently visited NPCs");
  206. end
  207. end
  208. function TI_IsNPCOn(npcname, type)
  209. local opton = false;
  210. if(type ~= nil) then
  211. for k,v in pairs(TI_status.options) do
  212. if(v.type == type and v.state == true) then
  213. opton = true;
  214. end
  215. end
  216. else
  217. opton = true;
  218. end
  219. if(TI_NPCDB[npcname] == nil and TI_status.usedefault == true and opton == true) then
  220. TI_debug("case 1");
  221. return true;
  222. elseif(TI_NPCDB[npcname] ~= nil and TI_NPCDB[npcname].state) then
  223. TI_debug("case 2");
  224. return true;
  225. elseif(TI_NPCDB[npcname] ~= nil and TI_status.usedefault == true and not TI_NPCDB[npcname].state and opton == true) then
  226. TI_debug("case 4");
  227. return true;
  228. else
  229. TI_debug("case 3");
  230. return false;
  231. end
  232. end
  233. function TI_OnEvent(self, event, ...)
  234. if(event == "VARIABLES_LOADED") then
  235. TI_VarInit();
  236. if(TI_status.state) then
  237. TI_LoadEvents();
  238. end
  239. end
  240. if(TI_status.state and not IsShiftKeyDown()) then
  241. if(event == "QUEST_GREETING") then
  242. TI_debug("Quest Greeting");
  243. TI_lastquestframe = "greeting";
  244. if(QuestFrame:IsVisible()) then
  245. if(TI_gossipclosed) then
  246. TI_debug("resetting pointers");
  247. TI_gossipclosed = false;
  248. TI_ResetPointers();
  249. end
  250. TI_HandleGossipWindow("q");
  251. end
  252. end
  253. if(event == "GOSSIP_SHOW") then
  254. TI_debug("Gossip Show");
  255. if(GossipFrame:IsVisible()) then
  256. if(TI_gossipclosed) then
  257. TI_debug("resetting pointers");
  258. TI_gossipclosed = false;
  259. TI_ResetPointers();
  260. end
  261. TI_HandleGossipWindow("g");
  262. end
  263. end
  264. if(event == "GOSSIP_CLOSED") then
  265. TI_debug("Gossip Closed");
  266. if(not GossipFrame:IsVisible()) then
  267. TI_gossipclosed = true;
  268. end;
  269. end
  270. if(event == "QUEST_COMPLETE") then
  271. TI_debug("Quest Complete");
  272. if(TI_IsNPCOn(UnitName("npc"), "activequest")) then
  273. if(not (GetNumQuestChoices() > 1)) then
  274. GetQuestReward(1);
  275. end
  276. end
  277. TI_ResetPointers();
  278. end
  279. if(event == "QUEST_PROGRESS") then
  280. TI_debug("Quest Progress");
  281. TI_gossipclosed = false;
  282. TI_lastquestframe = "progress";
  283. TI_HandleQuestProgress();
  284. end
  285. if(event == "QUEST_DETAIL") then
  286. TI_debug("Quest Detail");
  287. TI_gossipclosed = false;
  288. if(TI_IsNPCOn(UnitName("npc"), "availquest")) then
  289. TI_HandleAcceptQuest();
  290. end
  291. TI_ResetPointers();
  292. end
  293. if(event == "QUEST_FINISHED") then
  294. TI_debug("Quest Finished");
  295. if(not QuestFrame:IsVisible() and TI_lastquestframe == "greeting") then
  296. TI_debug("looks like the quest frame closed, resetting pointers on next open");
  297. TI_gossipclosed = true;
  298. end
  299. end
  300. end
  301. end
  302. function TI_HandleAcceptQuest()
  303. --QuestInfoFadingFrame_OnUpdate(QuestInfoFadingFrame, 1); -- does not exist anymore in 6.0
  304. QuestDetailAcceptButton_OnClick();
  305. end
  306. function TI_HandleQuestProgress()
  307. local questname = GetTitleText();
  308. local npcname = UnitName("npc");
  309. if(QuestFrame:IsVisible()) then
  310. if(TI_NPCDB[npcname]) then
  311. local thisnpc = TI_NPCDB[npcname];
  312. if(thisnpc.state) then
  313. for i,current in ipairs(thisnpc) do
  314. if(current.name == questname and current.state) then
  315. TI_CompleteQuest();
  316. end
  317. end
  318. else
  319. if(TI_IsNPCOn(UnitName("npc"), "activequest")) then
  320. TI_CompleteQuest();
  321. end
  322. end
  323. else
  324. if(TI_IsNPCOn(UnitName("npc"), "activequest")) then
  325. TI_CompleteQuest();
  326. end
  327. end
  328. end
  329. end
  330. function TI_CompleteQuest()
  331. if(IsQuestCompletable()) then
  332. TI_debug("quest is completable, completeing");
  333. CompleteQuest();
  334. TI_ResetPointers();
  335. else
  336. TI_debug("quest is not completable, declining");
  337. QuestDetailDeclineButton_OnClick();
  338. end
  339. end
  340. function TI_GetQuests(type)
  341. local numQuests = (getglobal("GetNum"..type.."Quests"))();
  342. local qfn = getglobal("Get"..type.."Title");
  343. local ret = {};
  344. local qname;
  345. local i=1;
  346. for i=1,numQuests do
  347. qname = qfn(i);
  348. ret[i] = {name=qname};
  349. end
  350. return ret;
  351. end
  352. function TI_HandleGossipWindow(gorq)
  353. local SAcQ;
  354. local SAvQ;
  355. local AvailableQuests;
  356. local ActiveQuests;
  357. local GossipOptions;
  358. if(gorq == "g") then
  359. AvailableQuests = TI_TabulateGossipAvailableQuests(GetGossipAvailableQuests());
  360. ActiveQuests = TI_TabulateGossipActiveQuests(GetGossipActiveQuests());
  361. GossipOptions = TI_TabulateGossipOptions(GetGossipOptions());
  362. SAcQ = SelectGossipActiveQuest;
  363. SAvQ = SelectGossipAvailableQuest;
  364. elseif(gorq == "q") then
  365. AvailableQuests = TI_GetQuests("Available");
  366. ActiveQuests = TI_GetQuests("Active");
  367. GossipOptions = {};
  368. SAcQ=SelectActiveQuest;
  369. SAvQ=SelectAvailableQuest;
  370. end
  371. local ListEntry = {};
  372. for i,v in ipairs(AvailableQuests) do
  373. local x={};
  374. x.name = v.name;
  375. x.gorq = gorq;
  376. x.args = i;
  377. x.type = "availquest";
  378. x.icon = v.icon;
  379. x.state = false;
  380. table.insert(ListEntry, x);
  381. end
  382. for i,v in ipairs(ActiveQuests) do
  383. local x={};
  384. x.name = v.name;
  385. x.gorq = gorq;
  386. x.args = i;
  387. x.type = "activequest";
  388. x.icon = v.icon;
  389. x.state = false;
  390. table.insert(ListEntry, x);
  391. end
  392. for i,v in ipairs(GossipOptions) do
  393. local x={};
  394. x.name = v.name;
  395. x.gorq = gorq;
  396. x.args = i;
  397. x.type = v.type;
  398. x.state = false;
  399. table.insert(ListEntry, x);
  400. end
  401. ListEntry.state = false;
  402. local TotalOptions = #AvailableQuests+#ActiveQuests+#GossipOptions;
  403. --if(TI_activenumber > ActiveQuests.getn()) TI_activenumber = 1;
  404. if(TotalOptions < 1) then
  405. return;
  406. end
  407. local npcname = UnitName("npc");
  408. TI_AddNPCToTempList(npcname, ListEntry);
  409. if(TI_status.autoadd and (not TI_NPCDB[npcname])) then
  410. TI_debug("autoadd on, adding this NPC", TI_status.autoadd, TI_NPCDB[npcname]);
  411. TI_AddNPCToList(ListEntry, npcname);
  412. end
  413. -- If a NPC is in the Database but a new dialog option has appeared (new daily/completed quest) then add it to the DB
  414. if (TI_NPCDB[npcname]) then
  415. for k1, v1 in ipairs(ListEntry) do
  416. local found = false;
  417. for k2, v2 in ipairs(TI_NPCDB[npcname]) do
  418. if (v2.type == v1.type and v2.name == v1.name) then
  419. found = true;
  420. end
  421. end
  422. if (not found) then
  423. table.insert(TI_NPCDB[npcname], v1)
  424. TI_PopulateOptions("npclist updated");
  425. end
  426. end
  427. end
  428. if(TI_availnumber > TotalOptions or TI_activenumber > TotalOptions) then
  429. TI_ResetPointers();
  430. return;
  431. end
  432. TI_debug(npcname);
  433. if(TI_NPCDB[npcname]) then
  434. local thisnpc = TI_NPCDB[npcname];
  435. if(thisnpc.state) then
  436. TI_debug("npc is active, using his options");
  437. for i1,current in ipairs(thisnpc) do
  438. if (current.state == true) then
  439. TI_debug("Current Quest: "..current.name);
  440. if (TI_specnum == 0 or i1 > TI_specnum) then
  441. TI_specnum = i1;
  442. if (current.type == "availquest") then
  443. for i2,v2 in ipairs(AvailableQuests) do
  444. if (v2.name == current.name) then
  445. TI_debug(i1.."-Available Match Found: "..current.name);
  446. TI_FunctionList[current.gorq]["availquest"](i2);
  447. return;
  448. end
  449. end
  450. elseif (current.type == "activequest") then
  451. for i2,v2 in ipairs(ActiveQuests) do
  452. if (v2.name == current.name) then
  453. TI_debug(i1.."-Active Match Found: "..current.name..", "..current.type);
  454. TI_FunctionList[current.gorq]["activequest"](i2);
  455. return;
  456. end
  457. end
  458. else
  459. for i2,v2 in ipairs(GossipOptions) do
  460. if (v2.name == current.name) then
  461. TI_debug(i1.."-Gossip Match Found: "..current.name..", "..current.type);
  462. SelectGossipOption(i2);
  463. return;
  464. end
  465. end
  466. end
  467. end
  468. end
  469. end
  470. return;
  471. else
  472. TI_debug("npc in list, but not active");
  473. end
  474. else
  475. TI_debug("npc not in list");
  476. end
  477. if(TI_status.usedefault == false) then
  478. TI_debug("npc not in list, default set to off, returning.");
  479. return;
  480. end;
  481. TI_debug("using default config");
  482. for i,current in ipairs(TI_status.options) do
  483. if(current.state) then
  484. if(current.type == "availquest" and #AvailableQuests > 0 and TI_availnumber <= #AvailableQuests) then
  485. TI_availnumber = TI_availnumber + 1;
  486. SAvQ(TI_availnumber-1);
  487. TI_debug("Selecting Available Quest ".. TI_availnumber-1);
  488. return;
  489. elseif(current.type == "activequest" and #ActiveQuests > 0 and TI_activenumber <= #ActiveQuests) then
  490. TI_activenumber = TI_activenumber + 1;
  491. SAcQ(TI_activenumber-1);
  492. TI_debug("Selecting Active Quest ".. TI_activenumber-1);
  493. return;
  494. elseif(#GossipOptions > 0) then
  495. for j,val in ipairs(GossipOptions) do
  496. if(val.type == current.type) then
  497. TI_ResetPointers();
  498. SelectGossipOption(j);
  499. return;
  500. end
  501. end
  502. end
  503. end
  504. end
  505. end
  506. function TI_AddNPCToList(OptList, npcname, confirminquestion)
  507. if (npcname == nil) then
  508. npcname = UnitName("npc");
  509. end
  510. if(#OptList > 0) then
  511. if(TI_NPCDB[npcname] == nil) then
  512. TI_NPCDB[npcname] = TI_copyTable(OptList);
  513. table.insert(TI_NPCIndex, npcname);
  514. table.sort(TI_NPCIndex);
  515. TI_PopulateOptions("npclist updated");
  516. elseif(confirminquestion == true) then
  517. TI_NPCDB[npcname] = TI_copyTable(OptList);
  518. TI_PopulateOptions("npclist updated");
  519. else
  520. TI_NPCInQuestion = {name=npcname, list=OptList};
  521. StaticPopup_Show("TI_NPCINQUESTION", npcname);
  522. end
  523. end
  524. end
  525. function TI_AddNPCToTempList(name, list)
  526. local temp = {};
  527. temp.name = name;
  528. temp.list = list;
  529. temp.location = GetSubZoneText() .. ", " .. GetRealZoneText();
  530. table.insert(TI_TempNPCList, 1, temp);
  531. if(#TI_TempNPCList > TI_TempNPCListMaxSize) then
  532. table.remove(TI_TempNPCList, TI_TempNPCListMaxSize);
  533. end
  534. TI_TempNPCListUpdate();
  535. end
  536. function TI_DeleteTempNPCIndex(index)
  537. table.remove(TI_TempNPCList, index);
  538. TI_TempNPCListUpdate();
  539. end
  540. function TI_AddTempNPCIndex(index)
  541. TI_AddNPCToList(TI_TempNPCList[index].list, TI_TempNPCList[index].name);
  542. end
  543. function TI_DeleteNPC(index)
  544. local name = TI_NPCIndex[index];
  545. table.remove(TI_NPCIndex, index);
  546. TI_NPCDB[name] = nil;
  547. end
  548. function TI_StripDescriptors(...)
  549. local x = {};
  550. local arg = {...};
  551. for i=1, #arg, 2 do
  552. table.insert(x,arg[i]);
  553. end
  554. return x;
  555. end
  556. function TI_TabulateGossipOptions(...)
  557. local x = {};
  558. local arg = {...};
  559. for i=1, #arg, 2 do
  560. local temp = {};
  561. temp.name = arg[i];
  562. temp.type = arg[i+1];
  563. table.insert(x, temp);
  564. end
  565. return x;
  566. end
  567. function TI_TabulateGossipAvailableQuests(...)
  568. local x = {};
  569. for i=1, select("#", ...), 6 do
  570. local temp = {};
  571. temp.name = select(i, ...);
  572. local isTrivial = select(i+2, ...);
  573. local isDaily = select(i+3, ...);
  574. local isRepeatable = select(i+4, ...);
  575. local isLegendary = select(i+5, ...);
  576. if ( isDaily ) then
  577. temp.icon = "Interface\\GossipFrame\\DailyQuestIcon";
  578. elseif ( isRepeatable ) then
  579. temp.icon = "Interface\\GossipFrame\\DailyActiveQuestIcon";
  580. elseif ( isLegendary ) then
  581. temp.icon = "Interface\\GossipFrame\\AvailableLegendaryQuestIcon";
  582. else
  583. temp.icon = "Interface\\GossipFrame\\AvailableQuestIcon";
  584. end
  585. table.insert(x, temp);
  586. end
  587. return x;
  588. end
  589. function TI_TabulateGossipActiveQuests(...)
  590. local x = {};
  591. for i=1, select("#", ...), 5 do
  592. local temp = {};
  593. temp.name = select(i, ...);
  594. local isComplete = select(i+3, ...);
  595. local isLegendary = select(i+4, ...);
  596. if(isComplete) then
  597. if(isLegendary) then
  598. temp.icon = "Interface\\GossipFrame\\ActiveLegendaryQuestIcon";
  599. else
  600. temp.icon = "Interface\\GossipFrame\\ActiveQuestIcon";
  601. end
  602. else
  603. temp.icon = "Interface\\GossipFrame\\IncompleteQuestIcon";
  604. end
  605. table.insert(x, temp);
  606. end
  607. return x;
  608. end
  609. --[[this function stolen from WhisperCast by Sarris, whom I love dearly for his contribution to Paladins everywhere.
  610. ]]
  611. function TI_copyTable( src )
  612. local copy = {}
  613. for k1,v1 in pairs(src) do
  614. if ( type(v1) == "table" ) then
  615. copy[k1]=TI_copyTable(v1)
  616. else
  617. copy[k1]=v1
  618. end
  619. end
  620. return copy
  621. end
  622. function toggle(arg)
  623. if(arg) then
  624. return false;
  625. else
  626. return true;
  627. end
  628. end