void C4MenuItem::DoTextProgress(int32_t &riByVal) { // any progress to be done? if (TextDisplayProgress<0) return; // if this is an option or empty text, show it immediately if (IsSelectable || !*Caption) { TextDisplayProgress=-1; return; } // normal text: move forward in unbroken message, ignoring markup StdStrBuf sText(Caption); C4Markup MarkupChecker(false); const char *szPos = sText.getPtr(std::min<int>(TextDisplayProgress, sText.getLength())); while (riByVal && *szPos) { MarkupChecker.SkipTags(&szPos); if (!*szPos) break; --riByVal; // Advance one UTF-8 character uint32_t c = GetNextCharacter(&szPos); // Treat embedded images {{XXX}} as one entity if(c == '{' && *szPos == '{') { int32_t end = SCharPos('}', szPos); if(end > 0 && szPos[end+1] == '}') szPos += end + 2; } } if (!*szPos) TextDisplayProgress=-1; else TextDisplayProgress = szPos - Caption; }
bool C4GameSave::SaveScenarioSections() { // any scenario sections? if (!Game.pScenarioSections) return true; // prepare section filename int iWildcardPos = SCharPos('*', C4CFN_ScenarioSections); char fn[_MAX_FNAME+1]; // save all modified sections for (C4ScenarioSection *pSect = Game.pScenarioSections; pSect; pSect = pSect->pNext) { // compose section filename SCopy(C4CFN_ScenarioSections, fn); SDelete(fn, 1, iWildcardPos); SInsert(fn, pSect->name.getData(), iWildcardPos); // do not save self, because that is implied in CurrentScenarioSection and the main landscape/object data if (pSect == Game.pCurrentScenarioSection) pSaveGroup->DeleteEntry(fn); else if (pSect->fModified) { // modified section: delete current pSaveGroup->DeleteEntry(fn); // replace by new pSaveGroup->Add(pSect->temp_filename.getData(), fn); } } // done, success return true; }
void C4GraphicsOverlay::Read(const char **ppInput) { // deprecated assert(false && "C4GraphicsOverlay::Read: deprecated"); #if 0 const char *szReadFrom = *ppInput; // defaults eMode = MODE_None; pSourceGfx = NULL; *Action=0; dwBlitMode = 0; iPhase = 0; iID=0; // read ID SCopyUntil(szReadFrom, OSTR, ',', C4MaxName); szReadFrom += strlen(OSTR); if (*szReadFrom) ++szReadFrom; sscanf(OSTR, "%i", &iID); // read C4ID::Gfxname int32_t iLineLength = SLen(szReadFrom); // not C4ID::Name? if (iLineLength < 6 || szReadFrom[4]!=':' || szReadFrom[5]!=':') { DebugLog("C4Compiler error: Malformed graphics overlay definition!"); return; } // get ID char id[5]; SCopy(szReadFrom, id, 4); szReadFrom += 6; C4Def *pSrcDef = ::Definitions.ID2Def(C4Id(id)); // defaults to NULL for unloaded def if (pSrcDef) { char GfxName[C4MaxName+1]; SCopyUntil(szReadFrom, GfxName, ',', C4MaxName); szReadFrom += strlen(GfxName); if (*szReadFrom) ++szReadFrom; // get graphics - "C4ID::" leads to *szLine == NULL, and thus the default graphic of pSrcDef! pSourceGfx = pSrcDef->Graphics.Get(GfxName); } // read mode DWORD dwRead; SCopyUntil(szReadFrom, OSTR, ',', C4MaxName); szReadFrom += strlen(OSTR); if (*szReadFrom) ++szReadFrom; sscanf(OSTR, "%i", &dwRead); eMode = (Mode) dwRead; // read action SCopyUntil(szReadFrom, Action, ',', C4MaxName); szReadFrom += strlen(Action); if (*szReadFrom) ++szReadFrom; // read blit mode SCopyUntil(szReadFrom, OSTR, ',', C4MaxName); szReadFrom += strlen(OSTR); if (*szReadFrom) ++szReadFrom; sscanf(OSTR, "%i", &dwBlitMode); // read phase SCopyUntil(szReadFrom, OSTR, ',', C4MaxName); szReadFrom += strlen(OSTR); if (*szReadFrom) ++szReadFrom; sscanf(OSTR, "%i", &iPhase); // read transform if (*szReadFrom) ++szReadFrom; // '(' int32_t iScanCnt = sscanf(szReadFrom, "%f,%f,%f,%f,%f,%f,%d", &Transform.mat[0], &Transform.mat[1], &Transform.mat[2], &Transform.mat[3], &Transform.mat[4], &Transform.mat[5], &Transform.FlipDir); if (iScanCnt != 7) { DebugLog("C4Compiler: malformed C4CV_Transform"); } iScanCnt = SCharPos(')', szReadFrom); if (iScanCnt>=0) szReadFrom += iScanCnt+1; // assign ptr immediately after read overlay *ppInput = szReadFrom; // update used facet according to read data UpdateFacet(); #endif }
int32_t C4TextureMap::LoadMap(C4Group &hGroup, const char *szEntryName, BOOL *pOverloadMaterials, BOOL *pOverloadTextures) { char *bpMap; char szLine[100+1]; int32_t cnt, iIndex, iTextures = 0; // Load text file into memory if (!hGroup.LoadEntry(szEntryName,&bpMap,NULL,1)) return 0; // Scan text buffer lines for (cnt=0; SCopySegment(bpMap,cnt,szLine,0x0A,100); cnt++) if ( (szLine[0]!='#') && (SCharCount('=',szLine)==1) ) { SReplaceChar(szLine,0x0D,0x00); if (Inside<int32_t>( iIndex = strtol(szLine,NULL,10), 0, C4M_MaxTexIndex-1 )) { const char *szMapping = szLine+SCharPos('=',szLine)+1; StdStrBuf Material, Texture; Material.CopyUntil(szMapping, '-'); Texture.Copy(SSearch(szMapping, "-")); if (AddEntry(iIndex, Material.getData(), Texture.getData())) iTextures++; } } else { if (SEqual2(szLine, "OverloadMaterials")) { fOverloadMaterials = TRUE; if(pOverloadMaterials) *pOverloadMaterials = TRUE; } if (SEqual2(szLine, "OverloadTextures")) { fOverloadTextures = TRUE; if(pOverloadTextures) *pOverloadTextures = TRUE; } } // Delete buffer, return entry count delete [] bpMap; fEntriesAdded=false; return iTextures; }
bool C4ComponentHost::GetLanguageString(const char *szLanguage, StdStrBuf &rTarget) { const char *cptr; // No good parameters if (!szLanguage || !Data) return false; // Search for two-letter language identifier in text body, i.e. "DE:" char langindex[4] = ""; for (int clseg = 0; SCopySegment(szLanguage ? szLanguage : "", clseg, langindex, ',', 2); clseg++) { SAppend(":", langindex); if (cptr = SSearch(Data.getData(), langindex)) { // Return the according string int iEndPos = SCharPos('\r', cptr); if (iEndPos < 0) iEndPos = SCharPos('\n', cptr); if (iEndPos < 0) iEndPos = strlen(cptr); rTarget.Copy(cptr, iEndPos); return true; } } // Language string not found return false; }
bool FnParTexCol(C4String *mattex, uint8_t& fg, uint8_t& bg) { // Return index of material-texture definition for a single color // Defaults to underground (tunnel background) color. Prefix material with ^ to get overground (sky background) color, // or specify as mattex1:mattex2 for foreground-background pair. if (!mattex || !mattex->GetCStr()) return false; int sep_pos = SCharPos(':', mattex->GetCStr()); if (sep_pos == -1) { const char *cmattex = mattex->GetCStr(); bool ift = true; if (*cmattex == '^') { ift=false; ++cmattex; } uint8_t col; if (!TexColSingle(cmattex, col)) return false; fg = col; if (ift) bg = ::MapScript.pTexMap->DefaultBkgMatTex(fg); else bg = C4M_MaxTexIndex; // sky return true; } else { const char *cmattex = mattex->GetCStr(); std::string fg_mattex(cmattex, cmattex + sep_pos); std::string bg_mattex(cmattex + sep_pos + 1); uint8_t fg_col, bg_col; if (!TexColSingle(fg_mattex.c_str(), fg_col)) return false; if (!TexColSingle(bg_mattex.c_str(), bg_col)) return false; fg = fg_col; bg = bg_col; return true; } }
bool C4DefGraphics::Load(C4Group &hGroup, StdMeshSkeletonLoader &loader, bool fColorByOwner) { char Filename[_MAX_PATH+1]; *Filename=0; // load skeletons hGroup.ResetSearch(); while (hGroup.FindNextEntry("*", Filename, NULL, !!*Filename)) { if (!WildcardMatch(C4CFN_DefSkeleton, Filename) && !WildcardMatch(C4CFN_DefSkeletonXml, Filename)) continue; LoadSkeleton(hGroup, Filename, loader); } // Try from Mesh first if (!LoadMesh(hGroup, C4CFN_DefMesh, loader)) if(!LoadMesh(hGroup, C4CFN_DefMeshXml, loader)) LoadBitmap(hGroup, C4CFN_DefGraphics, C4CFN_ClrByOwner, C4CFN_NormalMap, fColorByOwner); // load additional graphics C4DefGraphics *pLastGraphics = this; const int32_t iOverlayWildcardPos = SCharPos('*', C4CFN_ClrByOwnerEx); hGroup.ResetSearch(); *Filename=0; const char* const AdditionalGraphics[] = { C4CFN_DefGraphicsEx, C4CFN_DefGraphicsExMesh, C4CFN_DefGraphicsExMeshXml, NULL }; while (hGroup.FindNextEntry("*", Filename, NULL, !!*Filename)) { for(const char* const* szWildcard = AdditionalGraphics; *szWildcard != NULL; ++szWildcard) { if(!WildcardMatch(*szWildcard, Filename)) continue; // skip def graphics if (SEqualNoCase(Filename, C4CFN_DefGraphics) || SEqualNoCase(Filename, C4CFN_DefMesh) || SEqualNoCase(Filename, C4CFN_DefMeshXml)) continue; // skip scaled def graphics if (WildcardMatch(C4CFN_DefGraphicsScaled, Filename)) continue; // get name char GrpName[_MAX_PATH+1]; const int32_t iWildcardPos = SCharPos('*', *szWildcard); SCopy(Filename + iWildcardPos, GrpName, _MAX_PATH); RemoveExtension(GrpName); // remove trailing number for scaled graphics int32_t extpos; int scale; if ((extpos = SCharLastPos('.', GrpName)) > -1) if (sscanf(GrpName+extpos+1, "%d", &scale) == 1) GrpName[extpos] = '\0'; // clip to max length GrpName[C4MaxName]=0; // create new graphics pLastGraphics->pNext = new C4AdditionalDefGraphics(pDef, GrpName); pLastGraphics = pLastGraphics->pNext; if(*szWildcard == AdditionalGraphics[0]) { // create overlay-filename char OverlayFn[_MAX_PATH+1]; if(fColorByOwner) { // GraphicsX.png -> OverlayX.png SCopy(C4CFN_ClrByOwnerEx, OverlayFn, _MAX_PATH); OverlayFn[iOverlayWildcardPos]=0; SAppend(Filename + iWildcardPos, OverlayFn); EnforceExtension(OverlayFn, GetExtension(C4CFN_ClrByOwnerEx)); } // create normal filename char NormalFn[_MAX_PATH+1]; SCopy(C4CFN_NormalMapEx, NormalFn, _MAX_PATH); NormalFn[iOverlayWildcardPos]=0; SAppend(Filename + iWildcardPos, NormalFn); EnforceExtension(NormalFn, GetExtension(C4CFN_NormalMapEx)); // load them if (!pLastGraphics->LoadBitmap(hGroup, Filename, fColorByOwner ? OverlayFn : NULL, NormalFn, fColorByOwner)) return false; } else { if (!pLastGraphics->LoadMesh(hGroup, Filename, loader)) return false; } } } // done, success return true; }
bool C4RankSystem::Load(C4Group &hGroup, const char *szFilenames, int DefRankBase, const char *szLanguage) { // clear any loaded rank names Clear(); assert(szFilenames); assert(szLanguage); // load new C4ComponentHost Ranks; if (!Ranks.LoadEx("Ranks", hGroup, szFilenames, szLanguage)) return false; size_t iSize = Ranks.GetDataSize(); if (!iSize) return false; szRankNames=new char[iSize+1]; memcpy(szRankNames, Ranks.GetData(), iSize * sizeof(char)); szRankNames[iSize]=0; Ranks.Close(); // replace line breaks by zero-chars unsigned int i=0; for (; i<iSize; ++i) if (szRankNames[i] == 0x0A || szRankNames[i] == 0x0D) szRankNames[i]=0; // count names char *pRank0=szRankNames, *pPos=szRankNames; for (i=0; i<iSize; ++i,++pPos) if (!*pPos) { // zero-character found: content? if (pPos-pRank0>0) { // rank extension? if (*pRank0 == '*') ++iRankExtNum; // no comment? else if (*pRank0 != '#') // no setting? if (SCharPos('=', pRank0) < 0) // count as name! ++iRankNum; } // advance pos pRank0=pPos+1; } // safety if (!iRankNum) { Clear(); return false; } // set default rank base RankBase=DefRankBase; // alloc lists pszRankNames = new char *[iRankNum]; if (iRankExtNum) pszRankExtensions = new char *[iRankExtNum]; // fill list with names // count names pRank0=szRankNames; pPos=szRankNames; char **pszCurrRank=pszRankNames; char **pszCurrRankExt=pszRankExtensions; for (i=0; i<iSize; ++i,++pPos) if (!*pPos) { // zero-character found: content? if (pPos-pRank0>0) // extension? if (*pRank0 == '*') { *pszCurrRankExt++ = pRank0+1; } // no comment? else if (*pRank0 != '#') { // check if it's a setting int iEqPos=SCharPos('=', pRank0); if (iEqPos >= 0) { // get name and value of setting pRank0[iEqPos]=0; char *szValue=pRank0+iEqPos+1; if (SEqual(pRank0, "Base")) // get rankbase // note that invalid numbers may cause desyncs here...not very likely though :) sscanf(szValue, "%d", &RankBase); } else // yeeehaa! it's a name! store it, store it! *pszCurrRank++=pRank0; } // advance pos pRank0=pPos+1; } // check rankbase if (!RankBase) RankBase=1000; // ranks read, success return true; }
bool C4MusicFileOgg::Init(const char *strFile) { // Clear previous Clear(); // Base init file if (!C4MusicFile::Init(strFile)) return false; // Initial file loading // Currently, the whole compressed file is kept in memory because reading/seeking inside C4Group is problematic. Uncompress while playing. // This uses about 50MB of RAM (for ala's music pack) and increases startup time a bit. // Later, this could be replaced with proper random access in c4group. Either replacing the file format or e.g. storing the current zlib state here // and then updating callbacks.read/seek/close/tell_func to read data from the group directly as needed char *file_contents; size_t file_size; if (!C4Group_ReadFile(FileName, &file_contents, &file_size)) return false; data.SetOwnedData((BYTE *)file_contents, file_size); // Prepare ogg reader vorbis_info* info; memset(&ogg_file, 0, sizeof(ogg_file)); ov_callbacks callbacks; callbacks.read_func = &::C4SoundLoaders::VorbisLoader::read_func; callbacks.seek_func = &::C4SoundLoaders::VorbisLoader::seek_func; callbacks.close_func = &::C4SoundLoaders::VorbisLoader::close_func; callbacks.tell_func = &::C4SoundLoaders::VorbisLoader::tell_func; // open using callbacks if (ov_open_callbacks(&data, &ogg_file, NULL, 0, callbacks) != 0) { ov_clear(&ogg_file); return false; } // get information about music info = ov_info(&ogg_file, -1); if (info->channels == 1) ogg_info.format = AL_FORMAT_MONO16; else ogg_info.format = AL_FORMAT_STEREO16; ogg_info.sample_rate = info->rate; ogg_info.sample_length = ov_time_total(&ogg_file, -1) / 1000.0; // Get categories from ogg comment header vorbis_comment *comment = ov_comment(&ogg_file, -1); const char *comment_id = "COMMENT="; int comment_id_len = strlen(comment_id); for (int i = 0; i < comment->comments; ++i) { if (comment->comment_lengths[i] > comment_id_len) { if (SEqual2NoCase(comment->user_comments[i], comment_id, comment_id_len)) { // Add all categories delimeted by ';' const char *categories_string = comment->user_comments[i] + comment_id_len; for (;;) { int delimeter = SCharPos(';', categories_string); StdCopyStrBuf category; category.Copy(categories_string, delimeter >= 0 ? delimeter : SLen(categories_string)); categories.push_back(category); if (delimeter < 0) break; categories_string += delimeter+1; } } } } // mark successfully loaded return loaded = true; }
bool C4MainMenu::MenuCommand(const char *szCommand, bool fIsCloseCommand) { // Determine player C4Player *pPlr = ::Players.Get(Player); // Activate if (SEqual2(szCommand,"ActivateMenu:")) { if (C4GameOverDlg::IsShown()) return false; // no new menus during game over dlg if (SEqual(szCommand+13,"Main")) return ActivateMain(Player); if (SEqual(szCommand+13,"Hostility")) return ActivateHostility(Player); if (SEqual(szCommand+13,"NewPlayer")) return ActivateNewPlayer(Player); if (SEqual(szCommand+13,"Goals")) { ::Control.DoInput(CID_PlrAction, C4ControlPlayerAction::ActivateGoalMenu(::Players.Get(Player)), CDT_Queue); return true; } if (SEqual(szCommand+13,"Rules")) return ActivateRules(Player); if (SEqual(szCommand+13,"Host")) return ActivateHost(Player); if (SEqual(szCommand+13,"Client")) return ActivateClient(Player); if (SEqual(szCommand+13,"Options")) return ActivateOptions(Player); if (SEqual(szCommand+13,"Display")) return ActivateDisplay(Player); if (SEqual(szCommand+13,"Save:Game")) return ActivateSavegame(Player); if (SEqual(szCommand+13,"TeamSel")) return pPlr ? pPlr->ActivateMenuTeamSelection(true) : false; if (SEqual(szCommand+13,"Surrender")) return ActivateSurrender(Player); if (SEqual(szCommand+13,"Observer")) return ActivateObserver(); } // JoinPlayer if (SEqual2(szCommand,"JoinPlayer:")) { // not in league or replay mode if (Game.Parameters.isLeague() || Game.C4S.Head.Replay) return false; // join player // 2do: not for observers and such? Players.JoinNew(szCommand+11); return true; } // SetHostility if (SEqual2(szCommand,"SetHostility:")) { // only if allowed if (!Game.Teams.IsHostilityChangeAllowed()) return false; int32_t iOpponent; sscanf(szCommand+13,"%i",&iOpponent); C4Player *pOpponent = ::Players.Get(iOpponent); if (!pOpponent || pOpponent->GetType() != C4PT_User) return false; ::Control.DoInput(CID_PlrAction, C4ControlPlayerAction::SetHostility(::Players.Get(Player), pOpponent, !::Players.HostilityDeclared(Player, pOpponent->Number)), CDT_Queue); return true; } // Abort if (SEqual2(szCommand,"Abort")) { FullScreen.ShowAbortDlg(); return true; } // Surrender if (SEqual2(szCommand,"Surrender")) { ::Control.DoInput(CID_PlrAction, C4ControlPlayerAction::Surrender(::Players.Get(Player)), CDT_Queue); return true; } // Save game if (SEqual2(szCommand, "Save:Game:")) { char strFilename[_MAX_PATH + 1]; SCopySegment(szCommand, 2, strFilename, ':', _MAX_PATH); char strTitle[_MAX_PATH + 1]; SCopy(szCommand + SCharPos(':', szCommand, 2) + 1, strTitle, _MAX_PATH); Game.QuickSave(strFilename, strTitle); ActivateSavegame(Player); return true; } // Kick if (SEqual2(szCommand,"Host:Kick:")) { int iClientID = atoi(szCommand+10); if (iClientID && ::Network.isEnabled()) { if (Game.Parameters.isLeague() && ::Players.GetAtClient(iClientID)) ::Network.Vote(VT_Kick, true, iClientID); else { C4Client *pClient = Game.Clients.getClientByID(iClientID); if (pClient) Game.Clients.CtrlRemove(pClient, LoadResStr("IDS_MSG_KICKBYMENU")); Close(true); } } return true; } // Part if (SEqual2(szCommand,"Part")) { if (::Network.isEnabled()) { if (Game.Parameters.isLeague() && ::Players.GetLocalByIndex(0)) ::Network.Vote(VT_Kick, true, ::Control.ClientID()); else { Game.RoundResults.EvaluateNetwork(C4RoundResults::NR_NetError, LoadResStr("IDS_ERR_GAMELEFTVIAPLAYERMENU")); ::Network.Clear(); } } return true; } // Options if (SEqual2(szCommand,"Options:")) { // Music if (SEqual(szCommand + 8, "Music")) { Application.MusicSystem.ToggleOnOff(); } // Sound if (SEqual(szCommand + 8, "Sound")) { if (Config.Sound.RXSound) { Application.SoundSystem.Clear(); Config.Sound.RXSound = false; } else { Config.Sound.RXSound = true; if (!Application.SoundSystem.Init()) { Log(LoadResStr("IDS_PRC_NOSND")); } } } // Reopen with updated options ActivateOptions(Player, GetSelection()); return true; } // Display if (SEqual2(szCommand,"Display:")) { // Upper board if (SEqual(szCommand + 8, "UpperBoard")) { Config.Graphics.UpperBoard = !Config.Graphics.UpperBoard; ::Viewports.RecalculateViewports(); } // FPS if (SEqual(szCommand + 8, "FPS")) Config.General.FPS = !Config.General.FPS; // Player names if (SEqual(szCommand + 8, "PlayerNames")) Config.Graphics.ShowCrewNames = !Config.Graphics.ShowCrewNames; // Clonk names if (SEqual(szCommand + 8, "ClonkNames")) Config.Graphics.ShowCrewCNames = !Config.Graphics.ShowCrewCNames; // Clock if (SEqual(szCommand + 8, "Clock")) Config.Graphics.ShowClock = !Config.Graphics.ShowClock; // Reopen with updated options ActivateDisplay(Player, GetSelection()); return true; } // Goal info if (SEqual2(szCommand,"Player:Goal:") || SEqual2(szCommand,"Player:Rule:")) { if (!ValidPlr(Player)) return false; // observers may not look at goal/rule info, because it requires queue activation Close(true); C4Object *pObj; C4ID idItem(szCommand+12); C4Def * pDef = C4Id2Def(idItem); if (pDef && (pObj = ::Objects.Find(pDef))) ::Control.DoInput(CID_PlrAction, C4ControlPlayerAction::ActivateGoal(::Players.Get(Player), pObj), CDT_Queue); else return false; return true; } // Team selection if (SEqual2(szCommand, "TeamSel:")) { Close(true); int32_t idTeam = atoi(szCommand+8); // OK, join this team if (pPlr) pPlr->DoTeamSelection(idTeam); return true; } // Team switch if (SEqual2(szCommand, "TeamSwitch:")) { Close(true); int32_t idTeam = atoi(szCommand+11); // check if it's still allowed if (!Game.Teams.IsTeamSwitchAllowed()) return false; // OK, join this team ::Control.DoInput(CID_PlrAction, C4ControlPlayerAction::SetTeam(::Players.Get(Player), idTeam), CDT_Queue); return true; } // Observe if (SEqual2(szCommand, "Observe:")) { const char *szObserverTarget = szCommand+8; C4Viewport *pVP = ::Viewports.GetViewport(NO_OWNER); if (pVP) // viewport may have closed meanwhile { if (SEqual(szObserverTarget, "Free")) { // free view pVP->Init(NO_OWNER, true); return true; } else { // view following player int32_t iPlr = atoi(szObserverTarget); if (ValidPlr(iPlr)) { pVP->Init(iPlr, true); return true; } } } return false; } // No valid command return false; }
bool C4Markup::Read(const char **ppText, bool fSkip) { char Tag[50]; C4MarkupTag *pNewTag=nullptr; int iTagLen,iParLen; // get tag if (!SCopyEnclosed(*ppText, '<', '>', Tag, 49)) return false; iTagLen=SLen(Tag); // split tag to name and pars char *szPars=nullptr; int iSPos; if ((iSPos=SCharPos(' ', Tag))>-1) { Tag[iSPos]=0; szPars=Tag+iSPos+1; } // closing tag? if (Tag[0]=='/') { // no parameters if (szPars) return false; if (!fSkip) { // is this the tag to be closed? if (!pLast) return false; if (!SEqual(pLast->TagName(), Tag+1)) return false; // close it delete Pop(); } } // italic else if (SEqual(Tag, "i")) { // no parameters if (szPars) return false; // create italic tag if (!fSkip) pNewTag=new C4MarkupTagItalic(); } // colored else if (SEqual(Tag, "c")) { // no parameters? if (!szPars) return false; if ((iParLen=SLen(szPars))>8) return false; if (!fSkip) { // get color value by parameter DWORD dwClr=0; for (int i=0; i<iParLen; ++i) { BYTE b; if (szPars[i]>='0' && szPars[i]<='9') b=szPars[i]-'0'; else if (szPars[i]>='a' && szPars[i]<='f') b=szPars[i]-'a'+10; else return false; dwClr|=(b<<((iParLen-i-1)*4)); } // adjust alpha if not given if (iParLen<=6) dwClr|=0xff000000; // create color tag pNewTag=new C4MarkupTagColor(dwClr); } } // unknown tag else return false; // add created tag if (pNewTag) Push(pNewTag); // advance past tag *ppText+=iTagLen+2; // success return true; }